diff --git a/.github/actions/build-jtreg/action.yml b/.github/actions/build-jtreg/action.yml
index 0ba9937fb45..a9c046e9dd9 100644
--- a/.github/actions/build-jtreg/action.yml
+++ b/.github/actions/build-jtreg/action.yml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2023, 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
diff --git a/.github/actions/get-bundles/action.yml b/.github/actions/get-bundles/action.yml
index 270d15159a0..a356aa9fd8d 100644
--- a/.github/actions/get-bundles/action.yml
+++ b/.github/actions/get-bundles/action.yml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2022, 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
diff --git a/.github/actions/get-gtest/action.yml b/.github/actions/get-gtest/action.yml
index d38d33eabd8..7a329460a6e 100644
--- a/.github/actions/get-gtest/action.yml
+++ b/.github/actions/get-gtest/action.yml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2022, 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
diff --git a/.github/actions/get-jtreg/action.yml b/.github/actions/get-jtreg/action.yml
index 4bb671d25d1..36c895fc59d 100644
--- a/.github/actions/get-jtreg/action.yml
+++ b/.github/actions/get-jtreg/action.yml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2023, 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
diff --git a/.github/actions/get-msys2/action.yml b/.github/actions/get-msys2/action.yml
index d93b6e3763b..308230ebf2e 100644
--- a/.github/actions/get-msys2/action.yml
+++ b/.github/actions/get-msys2/action.yml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2022, 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
diff --git a/.github/actions/upload-bundles/action.yml b/.github/actions/upload-bundles/action.yml
index ca5366f3d6c..78fb0a94bfd 100644
--- a/.github/actions/upload-bundles/action.yml
+++ b/.github/actions/upload-bundles/action.yml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2022, 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
diff --git a/.github/workflows/build-alpine-linux.yml b/.github/workflows/build-alpine-linux.yml
index a39b342a248..c39962fa07f 100644
--- a/.github/workflows/build-alpine-linux.yml
+++ b/.github/workflows/build-alpine-linux.yml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2024, 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
@@ -96,6 +96,8 @@ jobs:
--with-boot-jdk=${{ steps.bootjdk.outputs.path }}
--with-zlib=system
--with-jmod-compress=zip-1
+ --with-external-symbols-in-bundles=none
+ --with-native-debug-symbols-level=1
${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || (
echo "Dumping config.log:" &&
cat config.log &&
diff --git a/.github/workflows/build-cross-compile.yml b/.github/workflows/build-cross-compile.yml
index e70937f57b6..a0642d469aa 100644
--- a/.github/workflows/build-cross-compile.yml
+++ b/.github/workflows/build-cross-compile.yml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2022, 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
@@ -179,6 +179,8 @@ jobs:
--openjdk-target=${{ matrix.gnu-arch }}-linux-gnu${{ matrix.gnu-abi}}
--with-sysroot=sysroot
--with-jmod-compress=zip-1
+ --with-external-symbols-in-bundles=none
+ --with-native-debug-symbols-level=1
CC=${{ matrix.gnu-arch }}-linux-gnu${{ matrix.gnu-abi}}-gcc-${{ inputs.gcc-major-version }}
CXX=${{ matrix.gnu-arch }}-linux-gnu${{ matrix.gnu-abi}}-g++-${{ inputs.gcc-major-version }}
${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || (
diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml
index 0680dea6bbe..791b53a3f04 100644
--- a/.github/workflows/build-linux.yml
+++ b/.github/workflows/build-linux.yml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2022, 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
@@ -143,6 +143,8 @@ jobs:
--with-gtest=${{ steps.gtest.outputs.path }}
--with-zlib=system
--with-jmod-compress=zip-1
+ --with-external-symbols-in-bundles=none
+ --with-native-debug-symbols-level=1
${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || (
echo "Dumping config.log:" &&
cat config.log &&
diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml
index 0a12df668e5..484e616fad7 100644
--- a/.github/workflows/build-macos.yml
+++ b/.github/workflows/build-macos.yml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2022, 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
@@ -110,6 +110,8 @@ jobs:
--with-gtest=${{ steps.gtest.outputs.path }}
--with-zlib=system
--with-jmod-compress=zip-1
+ --with-external-symbols-in-bundles=none
+ --with-native-debug-symbols-level=1
${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || (
echo "Dumping config.log:" &&
cat config.log &&
diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml
index a3091b94cef..4dafc016a99 100644
--- a/.github/workflows/build-windows.yml
+++ b/.github/workflows/build-windows.yml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2022, 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
@@ -134,6 +134,7 @@ jobs:
--with-gtest=${{ steps.gtest.outputs.path }}
--with-msvc-toolset-version=${{ inputs.msvc-toolset-version }}
--with-jmod-compress=zip-1
+ --with-external-symbols-in-bundles=none
${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || (
echo "Dumping config.log:" &&
cat config.log &&
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 09e6ed65a47..85ec75f343c 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2022, 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
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f2c8916a369..8f33454305e 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2022, 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
diff --git a/doc/testing.html b/doc/testing.html
index 31f4fbd1778..195153c8612 100644
--- a/doc/testing.html
+++ b/doc/testing.html
@@ -72,6 +72,7 @@ id="toc-notes-for-specific-tests">Notes for Specific Tests
Non-US
locale
PKCS11 Tests
+SCTP Tests
Testing Ahead-of-time
Optimizations
@@ -621,6 +622,21 @@ element of the appropriate @Artifact class. (See
JTREG="JAVA_OPTIONS=-Djdk.test.lib.artifacts.nsslib-linux_aarch64=/path/to/NSS-libs"
For more notes about the PKCS11 tests, please refer to
test/jdk/sun/security/pkcs11/README.
+SCTP Tests
+The SCTP tests require the SCTP runtime library, which is often not
+installed by default in popular Linux distributions. Without this
+library, the SCTP tests will be skipped. If you want to enable the SCTP
+tests, you should install the SCTP library before running the tests.
+For distributions using the .deb packaging format and the apt tool
+(such as Debian, Ubuntu, etc.), try this:
+sudo apt install libsctp1
+sudo modprobe sctp
+lsmod | grep sctp
+For distributions using the .rpm packaging format and the dnf tool
+(such as Fedora, Red Hat, etc.), try this:
+sudo dnf install -y lksctp-tools
+sudo modprobe sctp
+lsmod | grep sctp
Testing Ahead-of-time
Optimizations
One way to improve test coverage of ahead-of-time (AOT) optimizations
diff --git a/doc/testing.md b/doc/testing.md
index b95f59de9fd..d0e54aab02b 100644
--- a/doc/testing.md
+++ b/doc/testing.md
@@ -640,6 +640,32 @@ $ make test TEST="jtreg:sun/security/pkcs11/Secmod/AddTrustedCert.java" \
For more notes about the PKCS11 tests, please refer to
test/jdk/sun/security/pkcs11/README.
+
+### SCTP Tests
+
+The SCTP tests require the SCTP runtime library, which is often not installed
+by default in popular Linux distributions. Without this library, the SCTP tests
+will be skipped. If you want to enable the SCTP tests, you should install the
+SCTP library before running the tests.
+
+For distributions using the .deb packaging format and the apt tool
+(such as Debian, Ubuntu, etc.), try this:
+
+```
+sudo apt install libsctp1
+sudo modprobe sctp
+lsmod | grep sctp
+```
+
+For distributions using the .rpm packaging format and the dnf tool
+(such as Fedora, Red Hat, etc.), try this:
+
+```
+sudo dnf install -y lksctp-tools
+sudo modprobe sctp
+lsmod | grep sctp
+```
+
### Testing Ahead-of-time Optimizations
One way to improve test coverage of ahead-of-time (AOT) optimizations in
diff --git a/make/Bundles.gmk b/make/Bundles.gmk
index 8161b3b0362..0b324e7e3f3 100644
--- a/make/Bundles.gmk
+++ b/make/Bundles.gmk
@@ -185,77 +185,30 @@ endif
ifneq ($(filter product-bundles% legacy-bundles, $(MAKECMDGOALS)), )
- SYMBOLS_EXCLUDE_PATTERN := %.debuginfo %.diz %.map
-
- # There may be files with spaces in the names, so use ShellFindFiles
- # explicitly.
+ # There may be files with spaces in the names, so use ShellFindFiles explicitly.
ALL_JDK_FILES := $(call ShellFindFiles, $(JDK_IMAGE_DIR))
- ifneq ($(JDK_IMAGE_DIR), $(JDK_SYMBOLS_IMAGE_DIR))
- ALL_JDK_SYMBOLS_FILES := $(call ShellFindFiles, $(JDK_SYMBOLS_IMAGE_DIR))
- else
- ALL_JDK_SYMBOLS_FILES := $(ALL_JDK_FILES)
- endif
ifneq ($(JDK_IMAGE_DIR), $(JDK_DEMOS_IMAGE_DIR))
ALL_JDK_DEMOS_FILES := $(call ShellFindFiles, $(JDK_DEMOS_IMAGE_DIR))
else
ALL_JDK_DEMOS_FILES := $(ALL_JDK_FILES)
endif
- # Create special filter rules when dealing with unzipped .dSYM directories on
- # macosx
- ifeq ($(call isTargetOs, macosx), true)
- ifeq ($(ZIP_EXTERNAL_DEBUG_SYMBOLS), false)
- JDK_SYMBOLS_EXCLUDE_PATTERN := $(addprefix %, \
- $(call containing, .dSYM/, $(patsubst $(JDK_IMAGE_DIR)/%, %, \
- $(ALL_JDK_SYMBOLS_FILES))))
- endif
- endif
-
- # Create special filter rules when dealing with debug symbols on windows
- ifeq ($(call isTargetOs, windows), true)
- ifeq ($(SHIP_DEBUG_SYMBOLS), )
- JDK_SYMBOLS_EXCLUDE_PATTERN := %.pdb
- endif
- endif
-
JDK_BUNDLE_FILES := \
$(filter-out \
- $(JDK_SYMBOLS_EXCLUDE_PATTERN) \
$(JDK_EXTRA_EXCLUDES) \
- $(SYMBOLS_EXCLUDE_PATTERN) \
$(JDK_IMAGE_HOMEDIR)/demo/% \
, \
$(ALL_JDK_FILES) \
)
- JDK_SYMBOLS_BUNDLE_FILES := \
- $(call FindFiles, $(SYMBOLS_IMAGE_DIR))
+ JDK_SYMBOLS_BUNDLE_FILES := $(call FindFiles, $(SYMBOLS_IMAGE_DIR))
TEST_DEMOS_BUNDLE_FILES := $(filter $(JDK_DEMOS_IMAGE_HOMEDIR)/demo/%, \
$(ALL_JDK_DEMOS_FILES))
ALL_JRE_FILES := $(call ShellFindFiles, $(JRE_IMAGE_DIR))
- # Create special filter rules when dealing with unzipped .dSYM directories on
- # macosx
- ifeq ($(OPENJDK_TARGET_OS), macosx)
- ifeq ($(ZIP_EXTERNAL_DEBUG_SYMBOLS), false)
- JRE_SYMBOLS_EXCLUDE_PATTERN := $(addprefix %, \
- $(call containing, .dSYM/, $(patsubst $(JRE_IMAGE_DIR)/%, %, $(ALL_JRE_FILES))))
- endif
- endif
-
- # Create special filter rules when dealing with debug symbols on windows
- ifeq ($(call isTargetOs, windows), true)
- ifeq ($(SHIP_DEBUG_SYMBOLS), )
- JRE_SYMBOLS_EXCLUDE_PATTERN := %.pdb
- endif
- endif
-
- JRE_BUNDLE_FILES := $(filter-out \
- $(JRE_SYMBOLS_EXCLUDE_PATTERN) \
- $(SYMBOLS_EXCLUDE_PATTERN), \
- $(ALL_JRE_FILES))
+ JRE_BUNDLE_FILES := $(ALL_JRE_FILES)
ifeq ($(MACOSX_CODESIGN_MODE), hardened)
# Macosx release build and code signing available.
diff --git a/make/CreateJmods.gmk b/make/CreateJmods.gmk
index 3280b24924a..32f107b6bb6 100644
--- a/make/CreateJmods.gmk
+++ b/make/CreateJmods.gmk
@@ -218,10 +218,14 @@ ifeq ($(call isTargetOs, windows), true)
ifeq ($(SHIP_DEBUG_SYMBOLS), )
JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.diz,*.pdb,*.map}'
else
- JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.diz,*.map}'
+ JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.map}'
endif
else
- JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.diz,*.debuginfo,*.dSYM/**,*.dSYM}'
+ ifeq ($(SHIP_DEBUG_SYMBOLS), )
+ JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.diz,*.debuginfo,*.dSYM/**,*.dSYM}'
+ else
+ JMOD_FLAGS += --exclude '**{_the.*,_*.marker*}'
+ endif
endif
# Unless we are creating a very large module, use the small tool JVM options
diff --git a/make/Docs.gmk b/make/Docs.gmk
index b105e1d8683..a8d40e078e2 100644
--- a/make/Docs.gmk
+++ b/make/Docs.gmk
@@ -93,16 +93,19 @@ JAVADOC_DISABLED_DOCLINT_WARNINGS := missing
JAVADOC_DISABLED_DOCLINT_PACKAGES := org.w3c.* javax.smartcardio
# The initial set of options for javadoc
+# -XDaccessInternalAPI is a temporary workaround, see 8373909
JAVADOC_OPTIONS := -use -keywords -notimestamp \
-serialwarn -encoding utf-8 -docencoding utf-8 -breakiterator \
-splitIndex --system none -javafx --expand-requires transitive \
- --override-methods=summary
+ --override-methods=summary \
+ -XDaccessInternalAPI
# The reference options must stay stable to allow for comparisons across the
# development cycle.
REFERENCE_OPTIONS := -XDignore.symbol.file=true -use -keywords -notimestamp \
-serialwarn -encoding utf-8 -breakiterator -splitIndex --system none \
- -html5 -javafx --expand-requires transitive
+ -html5 -javafx --expand-requires transitive \
+ -XDaccessInternalAPI
# Should we add DRAFT stamps to the generated javadoc?
ifeq ($(VERSION_IS_GA), true)
diff --git a/make/RunTestsPrebuiltSpec.gmk b/make/RunTestsPrebuiltSpec.gmk
index e9fd901c9e1..5fe559eafad 100644
--- a/make/RunTestsPrebuiltSpec.gmk
+++ b/make/RunTestsPrebuiltSpec.gmk
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2017, 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
diff --git a/make/autoconf/bootcycle-spec.gmk.template b/make/autoconf/bootcycle-spec.gmk.template
index d17a95e190a..eb564f40e3f 100644
--- a/make/autoconf/bootcycle-spec.gmk.template
+++ b/make/autoconf/bootcycle-spec.gmk.template
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 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
diff --git a/make/autoconf/buildjdk-spec.gmk.template b/make/autoconf/buildjdk-spec.gmk.template
index 924389b94e8..bb020842d59 100644
--- a/make/autoconf/buildjdk-spec.gmk.template
+++ b/make/autoconf/buildjdk-spec.gmk.template
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2015, 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
diff --git a/make/autoconf/compare.sh.template b/make/autoconf/compare.sh.template
index 84421035ab9..b17ddc16827 100644
--- a/make/autoconf/compare.sh.template
+++ b/make/autoconf/compare.sh.template
@@ -1,6 +1,6 @@
#!/bin/bash
#
-# Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2012, 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
diff --git a/make/autoconf/flags-cflags.m4 b/make/autoconf/flags-cflags.m4
index 6298bcae416..639c3852212 100644
--- a/make/autoconf/flags-cflags.m4
+++ b/make/autoconf/flags-cflags.m4
@@ -69,6 +69,19 @@ AC_DEFUN([FLAGS_SETUP_DEBUG_SYMBOLS],
# Debug prefix mapping if supported by compiler
DEBUG_PREFIX_CFLAGS=
+ UTIL_ARG_WITH(NAME: native-debug-symbols-level, TYPE: literal,
+ DEFAULT: [auto], VALID_VALUES: [auto 1 2 3],
+ CHECK_AVAILABLE: [
+ if test x$TOOLCHAIN_TYPE = xmicrosoft; then
+ AVAILABLE=false
+ fi
+ ],
+ DESC: [set the native debug symbol level (GCC and Clang only)],
+ DEFAULT_DESC: [toolchain default],
+ IF_AUTO: [
+ RESULT=""
+ ])
+
# Debug symbols
if test "x$TOOLCHAIN_TYPE" = xgcc; then
if test "x$ALLOW_ABSOLUTE_PATHS_IN_OUTPUT" = "xfalse"; then
@@ -93,8 +106,9 @@ AC_DEFUN([FLAGS_SETUP_DEBUG_SYMBOLS],
)
fi
- CFLAGS_DEBUG_SYMBOLS="-g -gdwarf-4"
- ASFLAGS_DEBUG_SYMBOLS="-g"
+ # Debug info level should follow the debug format to be effective.
+ CFLAGS_DEBUG_SYMBOLS="-gdwarf-4 -g${NATIVE_DEBUG_SYMBOLS_LEVEL}"
+ ASFLAGS_DEBUG_SYMBOLS="-g${NATIVE_DEBUG_SYMBOLS_LEVEL}"
elif test "x$TOOLCHAIN_TYPE" = xclang; then
if test "x$ALLOW_ABSOLUTE_PATHS_IN_OUTPUT" = "xfalse"; then
# Check if compiler supports -fdebug-prefix-map. If so, use that to make
@@ -113,8 +127,9 @@ AC_DEFUN([FLAGS_SETUP_DEBUG_SYMBOLS],
FLAGS_COMPILER_CHECK_ARGUMENTS(ARGUMENT: [${GDWARF_FLAGS}],
IF_FALSE: [GDWARF_FLAGS=""])
- CFLAGS_DEBUG_SYMBOLS="-g ${GDWARF_FLAGS}"
- ASFLAGS_DEBUG_SYMBOLS="-g"
+ # Debug info level should follow the debug format to be effective.
+ CFLAGS_DEBUG_SYMBOLS="${GDWARF_FLAGS} -g${NATIVE_DEBUG_SYMBOLS_LEVEL}"
+ ASFLAGS_DEBUG_SYMBOLS="-g${NATIVE_DEBUG_SYMBOLS_LEVEL}"
elif test "x$TOOLCHAIN_TYPE" = xmicrosoft; then
CFLAGS_DEBUG_SYMBOLS="-Z7"
fi
diff --git a/make/autoconf/hotspot.m4 b/make/autoconf/hotspot.m4
index 6dc46d17aa3..3adb1333fe5 100644
--- a/make/autoconf/hotspot.m4
+++ b/make/autoconf/hotspot.m4
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 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
diff --git a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4
index bb188778001..87d147d4f07 100644
--- a/make/autoconf/jdk-options.m4
+++ b/make/autoconf/jdk-options.m4
@@ -316,23 +316,36 @@ AC_DEFUN_ONCE([JDKOPT_SETUP_DEBUG_SYMBOLS],
AC_MSG_CHECKING([if we should add external native debug symbols to the shipped bundles])
AC_ARG_WITH([external-symbols-in-bundles],
[AS_HELP_STRING([--with-external-symbols-in-bundles],
- [which type of external native debug symbol information shall be shipped in product bundles (none, public, full)
- (e.g. ship full/stripped pdbs on Windows) @<:@none@:>@])])
+ [which type of external native debug symbol information shall be shipped with bundles/images (none, public, full).
+ @<:@none in release builds, full otherwise. --with-native-debug-symbols=external/zipped is a prerequisite. public is only supported on Windows@:>@])],
+ [],
+ [with_external_symbols_in_bundles=default])
if test "x$with_external_symbols_in_bundles" = x || test "x$with_external_symbols_in_bundles" = xnone ; then
AC_MSG_RESULT([no])
elif test "x$with_external_symbols_in_bundles" = xfull || test "x$with_external_symbols_in_bundles" = xpublic ; then
- if test "x$OPENJDK_TARGET_OS" != xwindows ; then
- AC_MSG_ERROR([--with-external-symbols-in-bundles currently only works on windows!])
- elif test "x$COPY_DEBUG_SYMBOLS" != xtrue ; then
- AC_MSG_ERROR([--with-external-symbols-in-bundles only works when --with-native-debug-symbols=external is used!])
- elif test "x$with_external_symbols_in_bundles" = xfull ; then
+ if test "x$COPY_DEBUG_SYMBOLS" != xtrue ; then
+ AC_MSG_ERROR([--with-external-symbols-in-bundles only works when --with-native-debug-symbols=external/zipped is used!])
+ elif test "x$with_external_symbols_in_bundles" = xpublic && test "x$OPENJDK_TARGET_OS" != xwindows ; then
+ AC_MSG_ERROR([--with-external-symbols-in-bundles=public is only supported on Windows!])
+ fi
+
+ if test "x$with_external_symbols_in_bundles" = xfull ; then
AC_MSG_RESULT([full])
SHIP_DEBUG_SYMBOLS=full
else
AC_MSG_RESULT([public])
SHIP_DEBUG_SYMBOLS=public
fi
+ elif test "x$with_external_symbols_in_bundles" = xdefault ; then
+ if test "x$DEBUG_LEVEL" = xrelease ; then
+ AC_MSG_RESULT([no (default)])
+ elif test "x$COPY_DEBUG_SYMBOLS" = xtrue ; then
+ AC_MSG_RESULT([full (default)])
+ SHIP_DEBUG_SYMBOLS=full
+ else
+ AC_MSG_RESULT([no (default, native debug symbols are not external/zipped)])
+ fi
else
AC_MSG_ERROR([$with_external_symbols_in_bundles is an unknown value for --with-external-symbols-in-bundles])
fi
diff --git a/make/autoconf/lib-bundled.m4 b/make/autoconf/lib-bundled.m4
index 3246697663c..a6266bec014 100644
--- a/make/autoconf/lib-bundled.m4
+++ b/make/autoconf/lib-bundled.m4
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 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
diff --git a/make/autoconf/platform.m4 b/make/autoconf/platform.m4
index 31451d0c37f..1b247e159ac 100644
--- a/make/autoconf/platform.m4
+++ b/make/autoconf/platform.m4
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 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
diff --git a/make/common/Utils.gmk b/make/common/Utils.gmk
index c0ebabca3f7..b4bb949d3ee 100644
--- a/make/common/Utils.gmk
+++ b/make/common/Utils.gmk
@@ -114,7 +114,7 @@ EscapeDollar = $(subst $$,\$$,$(subst \$$,$$,$(strip $1)))
################################################################################
# This macro works just like EscapeDollar above, but for #.
-EscapeHash = $(subst \#,\\\#,$(subst \\\#,\#,$(strip $1)))
+EscapeHash = $(subst $(HASH),\$(HASH),$(subst \$(HASH),$(HASH),$(strip $1)))
################################################################################
# This macro translates $ into $$ to protect the string from make itself.
diff --git a/make/devkit/createWindowsDevkit.sh b/make/devkit/createWindowsDevkit.sh
index 7c55605c776..b21557ed498 100644
--- a/make/devkit/createWindowsDevkit.sh
+++ b/make/devkit/createWindowsDevkit.sh
@@ -1,6 +1,6 @@
#!/bin/bash
#
-# Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2015, 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
diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index 60912992134..327014b1e9d 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2016, 2026, 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
@@ -61,7 +61,8 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
INCLUDE_FILES := gtest-all.cc gmock-all.cc, \
DISABLED_WARNINGS_gcc := format-nonliteral maybe-uninitialized undef \
unused-result zero-as-null-pointer-constant, \
- DISABLED_WARNINGS_clang := format-nonliteral undef unused-result, \
+ DISABLED_WARNINGS_clang := format-nonliteral undef unused-result \
+ zero-as-null-pointer-constant, \
DISABLED_WARNINGS_microsoft := 4530, \
DEFAULT_CFLAGS := false, \
CFLAGS := $(JVM_CFLAGS) \
diff --git a/make/jdk/src/classes/build/tools/pandocfilter/PandocFilter.java b/make/jdk/src/classes/build/tools/pandocfilter/PandocFilter.java
index ebb49613e53..9f4d84ac95b 100644
--- a/make/jdk/src/classes/build/tools/pandocfilter/PandocFilter.java
+++ b/make/jdk/src/classes/build/tools/pandocfilter/PandocFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 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
diff --git a/make/jdk/src/classes/build/tools/taglet/JSpec.java b/make/jdk/src/classes/build/tools/taglet/JSpec.java
index ca45104e086..1c301e94a8a 100644
--- a/make/jdk/src/classes/build/tools/taglet/JSpec.java
+++ b/make/jdk/src/classes/build/tools/taglet/JSpec.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 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
@@ -31,10 +31,9 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import java.lang.reflect.Field;
import javax.lang.model.element.Element;
-import javax.lang.model.element.PackageElement;
-import javax.lang.model.element.TypeElement;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.LiteralTree;
@@ -160,9 +159,10 @@ public class JSpec implements Taglet {
if (m.find()) {
String chapter = m.group("chapter");
String section = m.group("section");
+ String rootParent = currentPath().replaceAll("[^/]+", "..");
- String url = String.format("%1$s/../specs/%2$s/%2$s-%3$s.html#%2$s-%3$s%4$s",
- docRoot(elem), idPrefix, chapter, section);
+ String url = String.format("%1$s/specs/%2$s/%2$s-%3$s.html#%2$s-%3$s%4$s",
+ rootParent, idPrefix, chapter, section);
sb.append(" CURRENT_PATH = null;
+
+ private String currentPath() {
+ if (CURRENT_PATH == null) {
+ try {
+ Field f = Class.forName("jdk.javadoc.internal.doclets.formats.html.HtmlDocletWriter")
+ .getField("CURRENT_PATH");
+ @SuppressWarnings("unchecked")
+ ThreadLocal tl = (ThreadLocal) f.get(null);
+ CURRENT_PATH = tl;
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException("Cannot determine current path", e);
+ }
+ }
+ return CURRENT_PATH.get();
+ }
private String expand(List extends DocTree> trees) {
return (new SimpleDocTreeVisitor() {
@@ -209,34 +225,4 @@ public class JSpec implements Taglet {
}).visit(trees, new StringBuilder()).toString();
}
- private String docRoot(Element elem) {
- switch (elem.getKind()) {
- case MODULE:
- return "..";
-
- case PACKAGE:
- PackageElement pe = (PackageElement)elem;
- String pkgPart = pe.getQualifiedName()
- .toString()
- .replace('.', '/')
- .replaceAll("[^/]+", "..");
- return pe.getEnclosingElement() != null
- ? "../" + pkgPart
- : pkgPart;
-
- case CLASS, ENUM, RECORD, INTERFACE, ANNOTATION_TYPE:
- TypeElement te = (TypeElement)elem;
- return te.getQualifiedName()
- .toString()
- .replace('.', '/')
- .replaceAll("[^/]+", "..");
-
- default:
- var enclosing = elem.getEnclosingElement();
- if (enclosing == null)
- throw new IllegalArgumentException(elem.getKind().toString());
- return docRoot(enclosing);
- }
- }
-
}
diff --git a/make/jdk/src/classes/build/tools/taglet/ToolGuide.java b/make/jdk/src/classes/build/tools/taglet/ToolGuide.java
index 72a8ab05b3b..8db2aee3092 100644
--- a/make/jdk/src/classes/build/tools/taglet/ToolGuide.java
+++ b/make/jdk/src/classes/build/tools/taglet/ToolGuide.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 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
@@ -31,10 +31,9 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import java.lang.reflect.Field;
import javax.lang.model.element.Element;
-import javax.lang.model.element.PackageElement;
-import javax.lang.model.element.TypeElement;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.UnknownBlockTagTree;
@@ -68,7 +67,7 @@ public class ToolGuide implements Taglet {
static final String TAG_NAME = "toolGuide";
- static final String BASE_URL = "../specs/man";
+ static final String BASE_URL = "specs/man";
static final Pattern TAG_PATTERN = Pattern.compile("(?s)(?[A-Za-z0-9]+)\\s*(?.*)$");
@@ -119,9 +118,10 @@ public class ToolGuide implements Taglet {
if (label.isEmpty()) {
label = name;
}
+ String rootParent = currentPath().replaceAll("[^/]+", "..");
String url = String.format("%s/%s/%s.html",
- docRoot(elem), BASE_URL, name);
+ rootParent, BASE_URL, name);
if (needComma) {
sb.append(",\n");
@@ -142,33 +142,21 @@ public class ToolGuide implements Taglet {
return sb.toString();
}
- private String docRoot(Element elem) {
- switch (elem.getKind()) {
- case MODULE:
- return "..";
+ private static ThreadLocal CURRENT_PATH = null;
- case PACKAGE:
- PackageElement pe = (PackageElement)elem;
- String pkgPart = pe.getQualifiedName()
- .toString()
- .replace('.', '/')
- .replaceAll("[^/]+", "..");
- return pe.getEnclosingElement() != null
- ? "../" + pkgPart
- : pkgPart;
-
- case CLASS, ENUM, RECORD, INTERFACE, ANNOTATION_TYPE:
- TypeElement te = (TypeElement)elem;
- return te.getQualifiedName()
- .toString()
- .replace('.', '/')
- .replaceAll("[^/]+", "..");
-
- default:
- var enclosing = elem.getEnclosingElement();
- if (enclosing == null)
- throw new IllegalArgumentException(elem.getKind().toString());
- return docRoot(enclosing);
+ private String currentPath() {
+ if (CURRENT_PATH == null) {
+ try {
+ Field f = Class.forName("jdk.javadoc.internal.doclets.formats.html.HtmlDocletWriter")
+ .getField("CURRENT_PATH");
+ @SuppressWarnings("unchecked")
+ ThreadLocal tl = (ThreadLocal) f.get(null);
+ CURRENT_PATH = tl;
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException("Cannot determine current path", e);
+ }
}
+ return CURRENT_PATH.get();
}
+
}
diff --git a/make/langtools/tools/propertiesparser/gen/ClassGenerator.java b/make/langtools/tools/propertiesparser/gen/ClassGenerator.java
index 247537b4676..14e8c4fb00a 100644
--- a/make/langtools/tools/propertiesparser/gen/ClassGenerator.java
+++ b/make/langtools/tools/propertiesparser/gen/ClassGenerator.java
@@ -286,7 +286,7 @@ public class ClassGenerator {
diagnosticFlags.isEmpty() ?
StubKind.DIAGNOSTIC_FLAGS_EMPTY.format() :
StubKind.DIAGNOSTIC_FLAGS_NON_EMPTY.format(diagnosticFlags),
- StubKind.LINT_CATEGORY.format("\"" + lintCategory + "\""),
+ StubKind.LINT_CATEGORY.format(toLintFieldName(lintCategory)),
"\"" + keyParts[0] + "\"",
"\"" + Stream.of(keyParts).skip(2).collect(Collectors.joining(".")) + "\"",
javadoc);
@@ -314,7 +314,7 @@ public class ClassGenerator {
diagnosticFlags.isEmpty() ?
StubKind.DIAGNOSTIC_FLAGS_EMPTY.format() :
StubKind.DIAGNOSTIC_FLAGS_NON_EMPTY.format(diagnosticFlags),
- StubKind.LINT_CATEGORY.format("\"" + lintCategory + "\""),
+ StubKind.LINT_CATEGORY.format(toLintFieldName(lintCategory)),
"\"" + keyParts[0] + "\"",
"\"" + Stream.of(keyParts).skip(2).collect(Collectors.joining(".")) + "\"",
argNames.stream().collect(Collectors.joining(", ")));
@@ -329,6 +329,11 @@ public class ClassGenerator {
}
}
+ String toLintFieldName(String lintCategory) {
+ return lintCategory.toUpperCase()
+ .replaceAll("-", "_");
+ }
+
/**
* Form the name of a factory method/field given a resource key.
*/
diff --git a/make/langtools/tools/propertiesparser/parser/Message.java b/make/langtools/tools/propertiesparser/parser/Message.java
index fb0809fb1bd..cbd1093a161 100644
--- a/make/langtools/tools/propertiesparser/parser/Message.java
+++ b/make/langtools/tools/propertiesparser/parser/Message.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 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
diff --git a/make/langtools/tools/propertiesparser/resources/templates.properties b/make/langtools/tools/propertiesparser/resources/templates.properties
index 81a9be2552c..f8ff07a878f 100644
--- a/make/langtools/tools/propertiesparser/resources/templates.properties
+++ b/make/langtools/tools/propertiesparser/resources/templates.properties
@@ -87,7 +87,7 @@ suppress.warnings=\
@SuppressWarnings("rawtypes")\n
lint.category=\
- LintCategory.get({0}).get()
+ LintCategory.{0}
diagnostic.flags.empty=\
EnumSet.noneOf(DiagnosticFlag.class)
diff --git a/make/modules/java.desktop/lib/ClientLibraries.gmk b/make/modules/java.desktop/lib/ClientLibraries.gmk
index f273065a6df..b76cb8dc4e3 100644
--- a/make/modules/java.desktop/lib/ClientLibraries.gmk
+++ b/make/modules/java.desktop/lib/ClientLibraries.gmk
@@ -164,7 +164,7 @@ ifeq ($(ENABLE_HEADLESS_ONLY), false)
ifeq ($(USE_EXTERNAL_LIBPNG), false)
LIBSPLASHSCREEN_HEADER_DIRS += libsplashscreen/libpng
- LIBSPLASHSCREEN_CFLAGS += -DPNG_NO_MMX_CODE -DPNG_ARM_NEON_OPT=0
+ LIBSPLASHSCREEN_CFLAGS += -DPNG_NO_MMX_CODE -DPNG_ARM_NEON_OPT=0 \
-DPNG_ARM_NEON_IMPLEMENTATION=0 -DPNG_LOONGARCH_LSX_OPT=0
ifeq ($(call isTargetOs, linux)+$(call isTargetCpuArch, ppc), true+true)
diff --git a/make/modules/jdk.jdwp.agent/Lib.gmk b/make/modules/jdk.jdwp.agent/Lib.gmk
index 6ae053d4bec..ce58e145f59 100644
--- a/make/modules/jdk.jdwp.agent/Lib.gmk
+++ b/make/modules/jdk.jdwp.agent/Lib.gmk
@@ -54,21 +54,6 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJDWP, \
NAME := jdwp, \
OPTIMIZATION := LOW, \
CFLAGS := -DJDWP_LOGGING $(ICONV_CFLAGS), \
- DISABLED_WARNINGS_gcc_eventFilter.c := unused-variable, \
- DISABLED_WARNINGS_gcc_SDE.c := unused-function, \
- DISABLED_WARNINGS_gcc_threadControl.c := unused-but-set-variable \
- unused-variable, \
- DISABLED_WARNINGS_gcc_utf_util.c := unused-but-set-variable, \
- DISABLED_WARNINGS_clang_error_messages.c := format-nonliteral, \
- DISABLED_WARNINGS_clang_eventFilter.c := unused-variable, \
- DISABLED_WARNINGS_clang_EventRequestImpl.c := self-assign, \
- DISABLED_WARNINGS_clang_inStream.c := sometimes-uninitialized, \
- DISABLED_WARNINGS_clang_log_messages.c := format-nonliteral, \
- DISABLED_WARNINGS_clang_SDE.c := unused-function, \
- DISABLED_WARNINGS_clang_threadControl.c := unused-but-set-variable \
- unused-variable, \
- DISABLED_WARNINGS_clang_utf_util.c := unused-but-set-variable, \
- DISABLED_WARNINGS_microsoft_debugInit.c := 5287, \
LDFLAGS := $(ICONV_LDFLAGS), \
EXTRA_HEADER_DIRS := \
include \
diff --git a/make/modules/jdk.security.auth/Lib.gmk b/make/modules/jdk.security.auth/Lib.gmk
index 9ead32dbe12..96d609f08d6 100644
--- a/make/modules/jdk.security.auth/Lib.gmk
+++ b/make/modules/jdk.security.auth/Lib.gmk
@@ -31,13 +31,14 @@ include LibCommon.gmk
## Build libjaas
################################################################################
-$(eval $(call SetupJdkLibrary, BUILD_LIBJAAS, \
- NAME := jaas, \
- OPTIMIZATION := LOW, \
- EXTRA_HEADER_DIRS := java.base:libjava, \
- LIBS_windows := advapi32.lib mpr.lib netapi32.lib user32.lib, \
-))
-
-TARGETS += $(BUILD_LIBJAAS)
+ifeq ($(call isTargetOs, windows), true)
+ $(eval $(call SetupJdkLibrary, BUILD_LIBJAAS, \
+ NAME := jaas, \
+ OPTIMIZATION := LOW, \
+ EXTRA_HEADER_DIRS := java.base:libjava, \
+ LIBS_windows := advapi32.lib mpr.lib netapi32.lib user32.lib, \
+ ))
+ TARGETS += $(BUILD_LIBJAAS)
+endif
################################################################################
diff --git a/make/scripts/compare-logger.sh b/make/scripts/compare-logger.sh
index 0a7d8b99f6f..930936f7ac0 100644
--- a/make/scripts/compare-logger.sh
+++ b/make/scripts/compare-logger.sh
@@ -1,6 +1,6 @@
#!/bin/bash
#
-# Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2012, 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
diff --git a/make/scripts/compare.sh b/make/scripts/compare.sh
index 250b5a37b9f..777db4b8242 100644
--- a/make/scripts/compare.sh
+++ b/make/scripts/compare.sh
@@ -1,6 +1,6 @@
#!/bin/bash
#
-# Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2012, 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
diff --git a/src/demo/share/java2d/J2DBench/Makefile b/src/demo/share/java2d/J2DBench/Makefile
index f8949845b4f..b5996ae4d6d 100644
--- a/src/demo/share/java2d/J2DBench/Makefile
+++ b/src/demo/share/java2d/J2DBench/Makefile
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
diff --git a/src/demo/share/java2d/J2DBench/build.xml b/src/demo/share/java2d/J2DBench/build.xml
index d48b446d0f3..748673b5fed 100644
--- a/src/demo/share/java2d/J2DBench/build.xml
+++ b/src/demo/share/java2d/J2DBench/build.xml
@@ -1,5 +1,5 @@
Name
- English buttons
+ English (United States)
Attributes
0x0000
Data
- AAYHRGV1dHNjaAtBa3plcHRpZXJlbghBYmxlaG5lbgdEcnVja2VuClNpY2hlcm4uLi7nS2xpY2tlbiBTaWUgaW4g0kFremVwdGllcmVu0ywgd2VubiBTaWUgbWl0IGRlbiBCZXN0aW1tdW5nZW4gZGVzIFNvZnR3YXJlLUxpemVuenZlcnRyYWdzIGVpbnZlcnN0YW5kZW4gc2luZC4gRmFsbHMgbmljaHQsIGJpdHRlINJBYmxlaG5lbtMgYW5rbGlja2VuLiBTaWUga5pubmVuIGRpZSBTb2Z0d2FyZSBudXIgaW5zdGFsbGllcmVuLCB3ZW5uIFNpZSDSQWt6ZXB0aWVyZW7TIGFuZ2VrbGlja3QgaGFiZW4u
+
+ STR_DATA_GERMAN
+
ID
5001
Name
@@ -41,151 +48,25 @@
Attributes
0x0000
Data
- AAYHRW5nbGlzaAVBZ3JlZQhEaXNhZ3JlZQVQcmludAdTYXZlLi4ue0lmIHlvdSBhZ3JlZSB3aXRoIHRoZSB0ZXJtcyBvZiB0aGlzIGxpY2Vuc2UsIHByZXNzICJBZ3JlZSIgdG8gaW5zdGFsbCB0aGUgc29mdHdhcmUuICBJZiB5b3UgZG8gbm90IGFncmVlLCBwcmVzcyAiRGlzYWdyZWUiLg==
+
+ STR_DATA_JAPANESE
+
ID
5002
Name
- English
-
-
- Attributes
- 0x0000
- Data
- AAYHRXNwYZZvbAdBY2VwdGFyCk5vIGFjZXB0YXIISW1wcmltaXIKR3VhcmRhci4uLsBTaSBlc3SHIGRlIGFjdWVyZG8gY29uIGxvcyB0jnJtaW5vcyBkZSBlc3RhIGxpY2VuY2lhLCBwdWxzZSAiQWNlcHRhciIgcGFyYSBpbnN0YWxhciBlbCBzb2Z0d2FyZS4gRW4gZWwgc3VwdWVzdG8gZGUgcXVlIG5vIGVzdI4gZGUgYWN1ZXJkbyBjb24gbG9zIHSOcm1pbm9zIGRlIGVzdGEgbGljZW5jaWEsIHB1bHNlICJObyBhY2VwdGFyLiI=
- ID
- 5003
- Name
- Spanish
-
-
- Attributes
- 0x0000
- Data
- AAYIRnJhbo1haXMIQWNjZXB0ZXIHUmVmdXNlcghJbXByaW1lcg5FbnJlZ2lzdHJlci4uLrpTaSB2b3VzIGFjY2VwdGV6IGxlcyB0ZXJtZXMgZGUgbGEgcHKOc2VudGUgbGljZW5jZSwgY2xpcXVleiBzdXIgIkFjY2VwdGVyIiBhZmluIGQnaW5zdGFsbGVyIGxlIGxvZ2ljaWVsLiBTaSB2b3VzIG4nkHRlcyBwYXMgZCdhY2NvcmQgYXZlYyBsZXMgdGVybWVzIGRlIGxhIGxpY2VuY2UsIGNsaXF1ZXogc3VyICJSZWZ1c2VyIi4=
- ID
- 5004
- Name
- French
-
-
- Attributes
- 0x0000
- Data
- AAYISXRhbGlhbm8HQWNjZXR0bwdSaWZpdXRvBlN0YW1wYQtSZWdpc3RyYS4uLn9TZSBhY2NldHRpIGxlIGNvbmRpemlvbmkgZGkgcXVlc3RhIGxpY2VuemEsIGZhaSBjbGljIHN1ICJBY2NldHRvIiBwZXIgaW5zdGFsbGFyZSBpbCBzb2Z0d2FyZS4gQWx0cmltZW50aSBmYWkgY2xpYyBzdSAiUmlmaXV0byIu
- ID
- 5005
- Name
- Italian
-
-
- Attributes
- 0x0000
- Data
- AAYISmFwYW5lc2UKk6+I04K1gtyCtwyTr4jTgrWC3IK5gvEIiPON/IK3gukHlduRti4uLrSWe4Ncg3SDZ4NFg0eDQY5nl3CLlpH4jF+W8YLMj/CMj4LJk6+I04KzguqC6Y/qjYeCyYLNgUGDXIN0g2eDRYNHg0GC8INDg5ODWINngVuDi4K3gumCvYLfgsmBdZOviNOCtYLcgreBdoLwiZ+CtYLEgq2CvoKzgqKBQoFAk6+I04KzguqCyIKij+qNh4LJgs2BQYF1k6+I04K1gtyCuYLxgXaC8ImfgrWCxIKtgr6Cs4KigUI=
- ID
- 5006
- Name
Japanese
Attributes
0x0000
Data
- AAYKTmVkZXJsYW5kcwJKYQNOZWUFUHJpbnQJQmV3YWFyLi4upEluZGllbiB1IGFra29vcmQgZ2FhdCBtZXQgZGUgdm9vcndhYXJkZW4gdmFuIGRlemUgbGljZW50aWUsIGt1bnQgdSBvcCAnSmEnIGtsaWtrZW4gb20gZGUgcHJvZ3JhbW1hdHV1ciB0ZSBpbnN0YWxsZXJlbi4gSW5kaWVuIHUgbmlldCBha2tvb3JkIGdhYXQsIGtsaWt0IHUgb3AgJ05lZScu
+
+ STR_DATA_SIMPLIFIED_CHINESE
+
ID
- 5007
+ 5003
Name
- Dutch
-
-
- Attributes
- 0x0000
- Data
- AAYGU3ZlbnNrCEdvZGuKbm5zBkF2YppqcwhTa3JpdiB1dAhTcGFyYS4uLpNPbSBEdSBnb2Rrim5uZXIgbGljZW5zdmlsbGtvcmVuIGtsaWNrYSBwjCAiR29ka4pubnMiIGaaciBhdHQgaW5zdGFsbGVyYSBwcm9ncmFtcHJvZHVrdGVuLiBPbSBEdSBpbnRlIGdvZGuKbm5lciBsaWNlbnN2aWxsa29yZW4sIGtsaWNrYSBwjCAiQXZimmpzIi4=
- ID
- 5008
- Name
- Swedish
-
-
- Attributes
- 0x0000
- Data
- AAYRUG9ydHVndZBzLCBCcmFzaWwJQ29uY29yZGFyCURpc2NvcmRhcghJbXByaW1pcglTYWx2YXIuLi6MU2UgZXN0hyBkZSBhY29yZG8gY29tIG9zIHRlcm1vcyBkZXN0YSBsaWNlbo1hLCBwcmVzc2lvbmUgIkNvbmNvcmRhciIgcGFyYSBpbnN0YWxhciBvIHNvZnR3YXJlLiBTZSBui28gZXN0hyBkZSBhY29yZG8sIHByZXNzaW9uZSAiRGlzY29yZGFyIi4=
- ID
- 5009
- Name
- Brazilian Portuguese
-
-
- Attributes
- 0x0000
- Data
- AAYSU2ltcGxpZmllZCBDaGluZXNlBM2s0uIGsrvNrNLiBLTy06EGtOa0oqGtVMjnufvE+s2s0uKxvtDtv8nQrdLptcTM9b/uo6zH67C0obDNrNLiobHAtLCy17C0y8jtvP6ho8jnufvE+rK7zazS4qOsx+uwtKGwsrvNrNLiobGhow==
- ID
- 5010
- Name
- Simplified Chinese
-
-
- Attributes
- 0x0000
- Data
- AAYTVHJhZGl0aW9uYWwgQ2hpbmVzZQSmULdOBqSjplC3TgSmQ6ZMBsB4pnOhS1CmcKpHsXqmULdOpbuzXKVpw9K4zKq6sfi02qFBvdCr9qGnplC3TqGopUimd7jLs27F6aFDpnCqR6SjplC3TqFBvdCr9qGnpKOmULdOoaihQw==
- ID
- 5011
- Name
- Traditional Chinese
-
-
- Attributes
- 0x0000
- Data
- AAYFRGFuc2sERW5pZwVVZW5pZwdVZHNrcml2CkFya2l2ZXIuLi6YSHZpcyBkdSBhY2NlcHRlcmVyIGJldGluZ2Vsc2VybmUgaSBsaWNlbnNhZnRhbGVuLCBza2FsIGR1IGtsaWtrZSBwjCDSRW5pZ9MgZm9yIGF0IGluc3RhbGxlcmUgc29mdHdhcmVuLiBLbGlrIHCMINJVZW5pZ9MgZm9yIGF0IGFubnVsbGVyZSBpbnN0YWxsZXJpbmdlbi4=
- ID
- 5012
- Name
- Danish
-
-
- Attributes
- 0x0000
- Data
- AAYFU3VvbWkISHl2imtzeW4KRW4gaHl2imtzeQdUdWxvc3RhCVRhbGxlbm5hyW9IeXaKa3N5IGxpc2Vuc3Npc29waW11a3NlbiBlaGRvdCBvc29pdHRhbWFsbGEg1Uh5doprc3nVLiBKb3MgZXQgaHl2imtzeSBzb3BpbXVrc2VuIGVodG9qYSwgb3NvaXRhINVFbiBoeXaKa3N51S4=
- ID
- 5013
- Name
- Finnish
-
-
- Attributes
- 0x0000
- Data
- AAYRRnJhbo1haXMgY2FuYWRpZW4IQWNjZXB0ZXIHUmVmdXNlcghJbXByaW1lcg5FbnJlZ2lzdHJlci4uLrpTaSB2b3VzIGFjY2VwdGV6IGxlcyB0ZXJtZXMgZGUgbGEgcHKOc2VudGUgbGljZW5jZSwgY2xpcXVleiBzdXIgIkFjY2VwdGVyIiBhZmluIGQnaW5zdGFsbGVyIGxlIGxvZ2ljaWVsLiBTaSB2b3VzIG4nkHRlcyBwYXMgZCdhY2NvcmQgYXZlYyBsZXMgdGVybWVzIGRlIGxhIGxpY2VuY2UsIGNsaXF1ZXogc3VyICJSZWZ1c2VyIi4=
- ID
- 5014
- Name
- French Canadian
-
-
- Attributes
- 0x0000
- Data
- AAYGS29yZWFuBLW/wMcJtb/AxyC+yMfUBsfBuLDGrgfA+sDlLi4ufrvnv+sgsOi+4LytwMcgs7u/67+hILW/wMfHz7jpLCAitb/AxyIgtNzD37imILStt68gvNLHwcauv/6+7rimILyzxKHHz73KvcO/wC4gtb/Ax8fPwfYgvsq0wrTZuOksICK1v8DHIL7Ix9QiILTcw9+4piC0qbijvcq9w7/ALg==
- ID
- 5015
- Name
- Korean
-
-
- Attributes
- 0x0000
- Data
- AAYFTm9yc2sERW5pZwlJa2tlIGVuaWcIU2tyaXYgdXQKQXJraXZlci4uLqNIdmlzIERlIGVyIGVuaWcgaSBiZXN0ZW1tZWxzZW5lIGkgZGVubmUgbGlzZW5zYXZ0YWxlbiwga2xpa2tlciBEZSBwjCAiRW5pZyIta25hcHBlbiBmb3IgjCBpbnN0YWxsZXJlIHByb2dyYW12YXJlbi4gSHZpcyBEZSBpa2tlIGVyIGVuaWcsIGtsaWtrZXIgRGUgcIwgIklra2UgZW5pZyIu
- ID
- 5016
- Name
- Norwegian
+ Chinese (Simplified)
TEXT
@@ -194,50 +75,49 @@
Attributes
0x0000
Data
- APPLICATION_LICENSE_TEXT
+
+ APPLICATION_LICENSE_TEXT
+
ID
5000
Name
- English SLA
+ English (United States) SLA
+
+
+ Attributes
+ 0x0000
+ Data
+
+ APPLICATION_LICENSE_TEXT
+
+ ID
+ 5001
+ Name
+ German SLA
-
- TMPL
-
Attributes
0x0000
Data
- E0RlZmF1bHQgTGFuZ3VhZ2UgSUREV1JEBUNvdW50T0NOVAQqKioqTFNUQwtzeXMgbGFuZyBJRERXUkQebG9jYWwgcmVzIElEIChvZmZzZXQgZnJvbSA1MDAwRFdSRBAyLWJ5dGUgbGFuZ3VhZ2U/RFdSRAQqKioqTFNURQ==
+
+ APPLICATION_LICENSE_TEXT
+
ID
- 128
+ 5002
Name
- LPic
+ Japanese SLA
-
- plst
-
-
- Attributes
- 0x0050
- Data
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- ID
- 0
- Name
-
-
-
- styl
-
Attributes
0x0000
Data
- AAMAAAAAAAwACQAUAAAAAAAAAAAAAAAAACcADAAJABQBAAAAAAAAAAAAAAAAKgAMAAkAFAAAAAAAAAAAAAA=
+
+ APPLICATION_LICENSE_TEXT
+
ID
- 5000
+ 5003
Name
- English SLA
+ Chinese (Simplified) SLA
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java
index b6b4322302e..75ead9d08ad 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, 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
@@ -28,11 +28,12 @@ import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toUnmodifiableMap;
import static java.util.stream.Collectors.toUnmodifiableSet;
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.APP_VERSION;
-import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_AS_SERVICE;
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.DESCRIPTION;
+import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_AS_SERVICE;
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_NAME;
import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
@@ -162,7 +163,23 @@ final class AppImageFile {
final var relativeAppImageFilePath = appImageDir.relativize(appImageFilePath);
try {
- final Document doc = XmlUtils.initDocumentBuilder().parse(Files.newInputStream(appImageFilePath));
+ //
+ // Use javax.xml.parsers.DocumentBuilder#parse(java.io.InputStream).
+ // Don't use javax.xml.parsers.DocumentBuilder#parse(java.io.File) as this will introduce
+ // dependency on how the XML parser reports filesystem I/O errors.
+ // E.g.: the default JDK XML parser throws java.io.FileNotFoundException if the supplied
+ // directory is not found and throws org.xml.sax.SAXParseException if the supplied file is a directory.
+ // Another DOM XML parser (a different version of Xerces?) may behave differently.
+ //
+ // The use of javax.xml.parsers.DocumentBuilder#parse(java.io.InputStream) eliminates
+ // differences in how XML parsers handle file system I/O errors.
+ // Filesystem I/O is delegated to java.nio.file.Files#readAllBytes(java.nio.file.Path),
+ // XML parser deals with the byte stream in memory and the error handling code
+ // doesn't depend on how XML parser reports filesystem I/O errors because
+ // it reads data from memory, not from the filesystem.
+ //
+ final Document doc = XmlUtils.initDocumentBuilder().parse(
+ new ByteArrayInputStream(Files.readAllBytes(appImageFilePath)));
final XPath xPath = XPathFactory.newInstance().newXPath();
@@ -198,7 +215,7 @@ final class AppImageFile {
} catch (XPathExpressionException ex) {
// This should never happen as XPath expressions should be correct
- throw ExceptionBox.rethrowUnchecked(ex);
+ throw ExceptionBox.toUnchecked(ex);
} catch (SAXException ex) {
// Malformed input XML
throw new JPackageException(I18N.format("error.malformed-app-image-file", relativeAppImageFilePath, appImageDir), ex);
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java
index 6896668ffdc..9d5407524a8 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, 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
@@ -27,6 +27,7 @@ package jdk.jpackage.internal;
import static jdk.jpackage.internal.I18N.buildConfigException;
import java.nio.file.Path;
+import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -44,6 +45,7 @@ import jdk.jpackage.internal.model.LauncherIcon;
import jdk.jpackage.internal.model.LauncherStartupInfo;
import jdk.jpackage.internal.model.ResourceDirLauncherIcon;
import jdk.jpackage.internal.model.RuntimeBuilder;
+import jdk.jpackage.internal.util.RootedPath;
final class ApplicationBuilder {
@@ -65,8 +67,8 @@ final class ApplicationBuilder {
Optional.ofNullable(version).orElseGet(DEFAULTS::version),
Optional.ofNullable(vendor).orElseGet(DEFAULTS::vendor),
Optional.ofNullable(copyright).orElseGet(DEFAULTS::copyright),
- Optional.ofNullable(srcDir),
- Optional.ofNullable(contentDirs).orElseGet(List::of),
+ Optional.ofNullable(appDirSources).orElseGet(List::of),
+ Optional.ofNullable(contentDirSources).orElseGet(List::of),
appImageLayout,
Optional.ofNullable(runtimeBuilder),
launchersAsList,
@@ -126,13 +128,13 @@ final class ApplicationBuilder {
return this;
}
- ApplicationBuilder srcDir(Path v) {
- srcDir = v;
+ ApplicationBuilder appDirSources(Collection v) {
+ appDirSources = v;
return this;
}
- ApplicationBuilder contentDirs(List v) {
- contentDirs = v;
+ ApplicationBuilder contentDirSources(Collection v) {
+ contentDirSources = v;
return this;
}
@@ -246,8 +248,8 @@ final class ApplicationBuilder {
app.version(),
app.vendor(),
app.copyright(),
- app.srcDir(),
- app.contentDirs(),
+ app.appDirSources(),
+ app.contentDirSources(),
Objects.requireNonNull(appImageLayout),
app.runtimeBuilder(),
app.launchers(),
@@ -294,9 +296,9 @@ final class ApplicationBuilder {
private String version;
private String vendor;
private String copyright;
- private Path srcDir;
+ private Collection appDirSources;
private ExternalApplication externalApp;
- private List contentDirs;
+ private Collection contentDirSources;
private AppImageLayout appImageLayout;
private RuntimeBuilder runtimeBuilder;
private ApplicationLaunchers launchers;
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationImageUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationImageUtils.java
index 3d2ffbfdc7c..5edc4d69c81 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationImageUtils.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationImageUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, 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
@@ -25,19 +25,14 @@
package jdk.jpackage.internal;
-import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer;
-
-import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
-import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.stream.Stream;
import jdk.jpackage.internal.PackagingPipeline.ApplicationImageTaskAction;
import jdk.jpackage.internal.model.Application;
import jdk.jpackage.internal.model.ApplicationLayout;
@@ -45,7 +40,7 @@ import jdk.jpackage.internal.model.CustomLauncherIcon;
import jdk.jpackage.internal.model.DefaultLauncherIcon;
import jdk.jpackage.internal.model.Launcher;
import jdk.jpackage.internal.model.ResourceDirLauncherIcon;
-import jdk.jpackage.internal.util.FileUtils;
+import jdk.jpackage.internal.util.RootedPath;
final class ApplicationImageUtils {
@@ -86,21 +81,14 @@ final class ApplicationImageUtils {
};
}
- static ApplicationImageTaskAction createCopyContentAction(Supplier> excludeCopyDirs) {
+ static ApplicationImageTaskAction createCopyContentAction() {
return env -> {
- var excludeCandidates = Stream.concat(
- excludeCopyDirs.get().stream(),
- Stream.of(env.env().buildRoot(), env.env().appImageDir())
- ).map(Path::toAbsolutePath).toList();
-
- env.app().srcDir().ifPresent(toConsumer(srcDir -> {
- copyRecursive(srcDir, env.resolvedLayout().appDirectory(), excludeCandidates);
- }));
-
- for (var srcDir : env.app().contentDirs()) {
- copyRecursive(srcDir,
- env.resolvedLayout().contentDirectory().resolve(srcDir.getFileName()),
- excludeCandidates);
+ for (var e : List.of(
+ Map.entry(env.app().appDirSources(), env.resolvedLayout().appDirectory()),
+ Map.entry(env.app().contentDirSources(), env.resolvedLayout().contentDirectory())
+ )) {
+ RootedPath.copy(e.getKey().stream(), e.getValue(),
+ StandardCopyOption.REPLACE_EXISTING, LinkOption.NOFOLLOW_LINKS);
}
};
}
@@ -126,21 +114,4 @@ final class ApplicationImageUtils {
}
};
}
-
- private static void copyRecursive(Path srcDir, Path dstDir, List excludeDirs) throws IOException {
- srcDir = srcDir.toAbsolutePath();
-
- List excludes = new ArrayList<>();
-
- for (var path : excludeDirs) {
- if (Files.isDirectory(path)) {
- if (path.startsWith(srcDir) && !Files.isSameFile(path, srcDir)) {
- excludes.add(path);
- }
- }
- }
-
- FileUtils.copyRecursive(srcDir, dstDir.toAbsolutePath(), excludes,
- LinkOption.NOFOLLOW_LINKS, StandardCopyOption.REPLACE_EXISTING);
- }
}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java
index 0bf7d6f41b2..331bde29d27 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, 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
@@ -42,17 +42,15 @@ import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
-import jdk.internal.util.OperatingSystem;
+import jdk.jpackage.internal.PackagingPipeline.PackageTaskID;
import jdk.jpackage.internal.cli.CliBundlingEnvironment;
import jdk.jpackage.internal.cli.Options;
import jdk.jpackage.internal.cli.StandardBundlingOperation;
-import jdk.jpackage.internal.model.AppImagePackageType;
import jdk.jpackage.internal.model.Application;
import jdk.jpackage.internal.model.BundlingOperationDescriptor;
import jdk.jpackage.internal.model.JPackageException;
import jdk.jpackage.internal.model.Package;
-import jdk.jpackage.internal.model.PackageType;
-import jdk.jpackage.internal.model.StandardPackageType;
+import jdk.jpackage.internal.util.PathUtils;
import jdk.jpackage.internal.util.Result;
class DefaultBundlingEnvironment implements CliBundlingEnvironment {
@@ -65,10 +63,10 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
Map>>> bundlers) {
this.bundlers = bundlers.entrySet().stream().collect(toMap(Map.Entry::getKey, e -> {
- return new CachingSupplier<>(e.getValue());
+ return runOnce(e.getValue());
}));
- this.defaultOperationSupplier = Objects.requireNonNull(defaultOperationSupplier).map(CachingSupplier::new);
+ this.defaultOperationSupplier = Objects.requireNonNull(defaultOperationSupplier).map(DefaultBundlingEnvironment::runOnce);
}
@@ -98,6 +96,11 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
return bundler(op, () -> Result.ofValue(bundler));
}
+ Builder mutate(Consumer mutator) {
+ mutator.accept(this);
+ return this;
+ }
+
private Supplier> defaultOperationSupplier;
private final Map>>> bundlers = new HashMap<>();
}
@@ -107,6 +110,10 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
return new Builder();
}
+ static Supplier runOnce(Supplier supplier) {
+ return new CachingSupplier<>(supplier);
+ }
+
static Supplier>> createBundlerSupplier(
Supplier> sysEnvResultSupplier, BiConsumer bundler) {
Objects.requireNonNull(sysEnvResultSupplier);
@@ -125,7 +132,9 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
Objects.requireNonNull(app);
Objects.requireNonNull(pipelineBuilder);
- final var outputDir = OptionUtils.outputDir(options).resolve(app.appImageDirName());
+ final var outputDir = PathUtils.normalizedAbsolutePath(OptionUtils.outputDir(options).resolve(app.appImageDirName()));
+
+ Log.verbose(I18N.getString("message.create-app-image"));
IOUtils.writableOutputDir(outputDir.getParent());
@@ -133,45 +142,51 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
.predefinedAppImageLayout(app.asApplicationLayout().orElseThrow())
.create(options, app);
- Log.verbose(I18N.format("message.creating-app-bundle", outputDir.getFileName(), outputDir.toAbsolutePath().getParent()));
-
if (Files.exists(outputDir)) {
- throw new JPackageException(I18N.format("error.root-exists", outputDir.toAbsolutePath()));
+ throw new JPackageException(I18N.format("error.root-exists", outputDir));
}
- pipelineBuilder.excludeDirFromCopying(outputDir.getParent())
- .create().execute(BuildEnv.withAppImageDir(env, outputDir), app);
+ pipelineBuilder.create().execute(BuildEnv.withAppImageDir(env, outputDir), app);
+
+ Log.verbose(I18N.getString("message.app-image-created"));
}
static void createNativePackage(Options options,
- Function createPackage,
+ T pkg,
BiFunction createBuildEnv,
PackagingPipeline.Builder pipelineBuilder,
Packager.PipelineBuilderMutatorFactory pipelineBuilderMutatorFactory) {
Objects.requireNonNull(pipelineBuilder);
- createNativePackage(options, createPackage, createBuildEnv, _ -> pipelineBuilder, pipelineBuilderMutatorFactory);
+ createNativePackage(options, pkg, createBuildEnv, _ -> pipelineBuilder, pipelineBuilderMutatorFactory);
}
static void createNativePackage(Options options,
- Function createPackage,
+ T pkg,
BiFunction createBuildEnv,
Function createPipelineBuilder,
Packager.PipelineBuilderMutatorFactory pipelineBuilderMutatorFactory) {
Objects.requireNonNull(options);
- Objects.requireNonNull(createPackage);
+ Objects.requireNonNull(pkg);
Objects.requireNonNull(createBuildEnv);
Objects.requireNonNull(createPipelineBuilder);
Objects.requireNonNull(pipelineBuilderMutatorFactory);
- var pkg = Objects.requireNonNull(createPackage.apply(options));
+ var pipelineBuilder = Objects.requireNonNull(createPipelineBuilder.apply(pkg));
+
+ // Delete an old output package file (if any) before creating a new one.
+ pipelineBuilder.task(PackageTaskID.DELETE_OLD_PACKAGE_FILE)
+ .addDependencies(pipelineBuilder.taskGraphSnapshot().getTailsOf(PackageTaskID.CREATE_PACKAGE_FILE))
+ .addDependent(PackageTaskID.CREATE_PACKAGE_FILE)
+ .packageAction(PackagingPipeline::deleteOutputBundle)
+ .add();
Packager.build().pkg(pkg)
- .outputDir(OptionUtils.outputDir(options))
- .env(Objects.requireNonNull(createBuildEnv.apply(options, pkg)))
- .pipelineBuilderMutatorFactory(pipelineBuilderMutatorFactory)
- .execute(Objects.requireNonNull(createPipelineBuilder.apply(pkg)));
+ .outputDir(OptionUtils.outputDir(options))
+ .env(Objects.requireNonNull(createBuildEnv.apply(options, pkg)))
+ .pipelineBuilderMutatorFactory(pipelineBuilderMutatorFactory)
+ .execute(pipelineBuilder);
}
@Override
@@ -188,10 +203,6 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
permanentWorkDirectory = Optional.of(tempDir.path());
}
bundler.accept(tempDir.options());
-
- var packageType = OptionUtils.bundlingOperation(cmdline).packageType();
-
- Log.verbose(I18N.format("message.bundle-created", I18N.getString(bundleTypeDescription(packageType, op.os()))));
} catch (IOException ex) {
throw new UncheckedIOException(ex);
} finally {
@@ -207,56 +218,9 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
}
private Supplier>> getBundlerSupplier(BundlingOperationDescriptor op) {
- return Optional.ofNullable(bundlers.get(op)).orElseThrow(NoSuchElementException::new);
- }
-
- private String bundleTypeDescription(PackageType type, OperatingSystem os) {
- switch (type) {
- case StandardPackageType stdType -> {
- switch (stdType) {
- case WIN_MSI -> {
- return "bundle-type.win-msi";
- }
- case WIN_EXE -> {
- return "bundle-type.win-exe";
- }
- case LINUX_DEB -> {
- return "bundle-type.linux-deb";
- }
- case LINUX_RPM -> {
- return "bundle-type.linux-rpm";
- }
- case MAC_DMG -> {
- return "bundle-type.mac-dmg";
- }
- case MAC_PKG -> {
- return "bundle-type.mac-pkg";
- }
- default -> {
- throw new AssertionError();
- }
- }
- }
- case AppImagePackageType appImageType -> {
- switch (os) {
- case WINDOWS -> {
- return "bundle-type.win-app";
- }
- case LINUX -> {
- return "bundle-type.linux-app";
- }
- case MACOS -> {
- return "bundle-type.mac-app";
- }
- default -> {
- throw new AssertionError();
- }
- }
- }
- default -> {
- throw new AssertionError();
- }
- }
+ return Optional.ofNullable(bundlers.get(op)).orElseThrow(() -> {
+ throw new NoSuchElementException(String.format("Unsupported bundling operation: %s", op));
+ });
}
@@ -279,5 +243,5 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
private final Map>>> bundlers;
- private final Optional>> defaultOperationSupplier;
+ private final Optional>> defaultOperationSupplier;
}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Executor.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Executor.java
index dd1cc4a24b4..53c587ab37c 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Executor.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Executor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, 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
@@ -25,53 +25,154 @@
package jdk.jpackage.internal;
import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.file.Files;
-import java.nio.file.Path;
+import java.io.PrintStream;
+import java.io.StringReader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
+import java.util.spi.ToolProvider;
import java.util.stream.Stream;
+import jdk.jpackage.internal.model.ExecutableAttributesWithCapturedOutput;
+import jdk.jpackage.internal.util.CommandLineFormat;
+import jdk.jpackage.internal.util.CommandOutputControl;
+import jdk.jpackage.internal.util.CommandOutputControl.ProcessAttributes;
+import jdk.jpackage.internal.util.CommandOutputControl.Result;
+import jdk.jpackage.internal.util.RetryExecutor;
+import jdk.jpackage.internal.util.function.ExceptionBox;
-public final class Executor {
+final class Executor {
- Executor() {
+ static Executor of(String... cmdline) {
+ return of(List.of(cmdline));
}
- Executor setOutputConsumer(Consumer> v) {
- outputConsumer = v;
- return this;
+ static Executor of(List cmdline) {
+ return of(new ProcessBuilder(cmdline));
+ }
+
+ static Executor of(ProcessBuilder pb) {
+ return Globals.instance().objectFactory().executor().processBuilder(pb);
+ }
+
+ public Executor() {
+ commandOutputControl = new CommandOutputControl();
+ args = new ArrayList<>();
+ }
+
+ private Executor(Executor other) {
+ commandOutputControl = other.commandOutputControl.copy();
+ quietCommand = other.quietCommand;
+ args = new ArrayList<>(other.args);
+ processBuilder = other.processBuilder;
+ toolProvider = other.toolProvider;
+ timeout = other.timeout;
+ mapper = other.mapper;
}
Executor saveOutput(boolean v) {
- saveOutput = v;
+ commandOutputControl.saveOutput(v);
return this;
}
- Executor setWriteOutputToFile(boolean v) {
- writeOutputToFile = v;
+ Executor saveOutput() {
+ return saveOutput(true);
+ }
+
+ Executor saveFirstLineOfOutput() {
+ commandOutputControl.saveFirstLineOfOutput();
return this;
}
- Executor setTimeout(long v) {
+ Executor charset(Charset v) {
+ commandOutputControl.charset(v);
+ return this;
+ }
+
+ Executor storeOutputInFiles(boolean v) {
+ commandOutputControl.storeOutputInFiles(v);
+ return this;
+ }
+
+ Executor storeOutputInFiles() {
+ return storeOutputInFiles(true);
+ }
+
+ Executor binaryOutput(boolean v) {
+ commandOutputControl.binaryOutput(v);
+ return this;
+ }
+
+ Executor binaryOutput() {
+ return binaryOutput(true);
+ }
+
+ Executor discardStdout(boolean v) {
+ commandOutputControl.discardStdout(v);
+ return this;
+ }
+
+ Executor discardStdout() {
+ return discardStdout(true);
+ }
+
+ Executor discardStderr(boolean v) {
+ commandOutputControl.discardStderr(v);
+ return this;
+ }
+
+ Executor discardStderr() {
+ return discardStderr(true);
+ }
+
+ Executor timeout(long v, TimeUnit unit) {
+ return timeout(Duration.of(v, unit.toChronoUnit()));
+ }
+
+ Executor timeout(Duration v) {
timeout = v;
- if (timeout != INFINITE_TIMEOUT) {
- // Redirect output to file if timeout is requested, otherwise we will
- // reading until process ends and timeout will never be reached.
- setWriteOutputToFile(true);
- }
return this;
}
- Executor setProcessBuilder(ProcessBuilder v) {
- pb = v;
+ Executor toolProvider(ToolProvider v) {
+ toolProvider = Objects.requireNonNull(v);
+ processBuilder = null;
return this;
}
- Executor setCommandLine(String... cmdline) {
- return setProcessBuilder(new ProcessBuilder(cmdline));
+ Optional toolProvider() {
+ return Optional.ofNullable(toolProvider);
+ }
+
+ Executor processBuilder(ProcessBuilder v) {
+ processBuilder = Objects.requireNonNull(v);
+ toolProvider = null;
+ return this;
+ }
+
+ Optional processBuilder() {
+ return Optional.ofNullable(processBuilder);
+ }
+
+ Executor args(List v) {
+ args.addAll(v);
+ return this;
+ }
+
+ Executor args(String... args) {
+ return args(List.of(args));
+ }
+
+ List args() {
+ return args;
}
Executor setQuiet(boolean v) {
@@ -79,159 +180,202 @@ public final class Executor {
return this;
}
- List getOutput() {
- return output;
- }
-
- Executor executeExpectSuccess() throws IOException {
- int ret = execute();
- if (0 != ret) {
- throw new IOException(
- String.format("Command %s exited with %d code",
- createLogMessage(pb, false), ret));
- }
+ Executor mapper(UnaryOperator v) {
+ mapper = v;
return this;
}
- int execute() throws IOException {
- output = null;
+ Optional> mapper() {
+ return Optional.ofNullable(mapper);
+ }
- boolean needProcessOutput = outputConsumer != null || Log.isVerbose() || saveOutput;
- Path outputFile = null;
- if (needProcessOutput) {
- pb.redirectErrorStream(true);
- if (writeOutputToFile) {
- outputFile = Files.createTempFile("jpackageOutputTempFile", ".tmp");
- pb.redirectOutput(outputFile.toFile());
+ Executor copy() {
+ return new Executor(this);
+ }
+
+ Result execute() throws IOException {
+ if (mapper != null) {
+ var mappedExecutor = Objects.requireNonNull(mapper.apply(this));
+ if (mappedExecutor != this) {
+ return mappedExecutor.execute();
}
+ }
+
+ var coc = commandOutputControl.copy();
+
+ final CommandOutputControl.Executable exec;
+ if (processBuilder != null) {
+ exec = coc.createExecutable(copyProcessBuilder());
+ } else if (toolProvider != null) {
+ exec = coc.createExecutable(toolProvider, args.toArray(String[]::new));
} else {
- // We are not going to read process output, so need to notify
- // ProcessBuilder about this. Otherwise some processes might just
- // hang up (`ldconfig -p`).
- pb.redirectError(ProcessBuilder.Redirect.DISCARD);
- pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
+ throw new IllegalStateException("No target to execute");
}
- if (!quietCommand) {
- Log.verbose(String.format("Running %s", createLogMessage(pb, true)));
- }
-
- Process p = pb.start();
-
- int code = 0;
- if (writeOutputToFile) {
- try {
- code = waitForProcess(p);
- } catch (InterruptedException ex) {
- Log.verbose(ex);
- throw new RuntimeException(ex);
- }
- }
-
- if (needProcessOutput) {
- final List savedOutput;
- Supplier> outputStream;
-
- if (writeOutputToFile) {
- output = savedOutput = Files.readAllLines(outputFile);
- Files.delete(outputFile);
- outputStream = () -> {
- if (savedOutput != null) {
- return savedOutput.stream();
- }
- return null;
- };
- if (outputConsumer != null) {
- outputConsumer.accept(outputStream.get());
- }
- } else {
- try (var br = new BufferedReader(new InputStreamReader(
- p.getInputStream()))) {
-
- if ((outputConsumer != null || Log.isVerbose())
- || saveOutput) {
- savedOutput = br.lines().toList();
- } else {
- savedOutput = null;
- }
- output = savedOutput;
-
- outputStream = () -> {
- if (savedOutput != null) {
- return savedOutput.stream();
- }
- return br.lines();
- };
- if (outputConsumer != null) {
- outputConsumer.accept(outputStream.get());
- }
-
- if (savedOutput == null) {
- // For some processes on Linux if the output stream
- // of the process is opened but not consumed, the process
- // would exit with code 141.
- // It turned out that reading just a single line of process
- // output fixes the problem, but let's process
- // all of the output, just in case.
- br.lines().forEach(x -> {});
- }
- }
- }
+ if (dumpOutput()) {
+ Log.verbose(String.format("Running %s", CommandLineFormat.DEFAULT.apply(List.of(commandLine().getFirst()))));
}
+ var printableOutputBuilder = new PrintableOutputBuilder(coc);
+ Result result;
try {
- if (!writeOutputToFile) {
- code = p.waitFor();
- }
- if (!quietCommand) {
- Log.verbose(pb.command(), getOutput(), code, IOUtils.getPID(p));
- }
- return code;
- } catch (InterruptedException ex) {
- Log.verbose(ex);
- throw new RuntimeException(ex);
- }
- }
-
- private int waitForProcess(Process p) throws InterruptedException {
- if (timeout == INFINITE_TIMEOUT) {
- return p.waitFor();
- } else {
- if (p.waitFor(timeout, TimeUnit.SECONDS)) {
- return p.exitValue();
+ if (timeout == null) {
+ result = exec.execute();
} else {
- Log.verbose(String.format("Command %s timeout after %d seconds",
- createLogMessage(pb, false), timeout));
- p.destroy();
- return -1;
+ result = exec.execute(timeout.toMillis(), TimeUnit.MILLISECONDS);
+ }
+ } catch (InterruptedException ex) {
+ throw ExceptionBox.toUnchecked(ex);
+ }
+
+ var printableOutput = printableOutputBuilder.create();
+ if (dumpOutput()) {
+ log(result, printableOutput);
+ }
+
+ return ExecutableAttributesWithCapturedOutput.augmentResultWithOutput(result, printableOutput);
+ }
+
+ Result executeExpectSuccess() throws IOException {
+ return execute().expectExitCode(0);
+ }
+
+ Result executeExpect(int mainExitCode, int... otherExitCodes) throws IOException {
+ return execute().expectExitCode(mainExitCode, otherExitCodes);
+ }
+
+ RetryExecutor retry() {
+ return Globals.instance().objectFactory().retryExecutor(IOException.class)
+ .setExecutable(this::executeExpectSuccess);
+ }
+
+ RetryExecutor retryOnKnownErrorMessage(String msg) {
+ Objects.requireNonNull(msg);
+ return saveOutput().retry().setExecutable(() -> {
+ // Execute it without exit code check.
+ var result = execute();
+ if (result.stderr().stream().anyMatch(msg::equals)) {
+ throw result.unexpected();
+ }
+ return result;
+ });
+ }
+
+ List commandLine() {
+ if (processBuilder != null) {
+ return Stream.of(processBuilder.command(), args).flatMap(Collection::stream).toList();
+ } else if (toolProvider != null) {
+ return Stream.concat(Stream.of(toolProvider.name()), args.stream()).toList();
+ } else {
+ throw new IllegalStateException("No target to execute");
+ }
+ }
+
+ private ProcessBuilder copyProcessBuilder() {
+ if (processBuilder == null) {
+ throw new IllegalStateException();
+ }
+
+ var copy = new ProcessBuilder(commandLine());
+ copy.directory(processBuilder.directory());
+ var env = copy.environment();
+ env.clear();
+ env.putAll(processBuilder.environment());
+
+ return copy;
+ }
+
+ private boolean dumpOutput() {
+ return Log.isVerbose() && !quietCommand;
+ }
+
+ private static void log(Result result, String printableOutput) throws IOException {
+ Objects.requireNonNull(result);
+ Objects.requireNonNull(printableOutput);
+
+ Optional pid;
+ if (result.execAttrs() instanceof ProcessAttributes attrs) {
+ pid = attrs.pid();
+ } else {
+ pid = Optional.empty();
+ }
+
+ var sb = new StringBuilder();
+ sb.append("Command");
+ pid.ifPresent(p -> {
+ sb.append(" [PID: ").append(p).append("]");
+ });
+ sb.append(":\n ").append(result.execAttrs().printableCommandLine());
+ Log.verbose(sb.toString());
+
+ if (!printableOutput.isEmpty()) {
+ sb.delete(0, sb.length());
+ sb.append("Output:");
+ try (var lines = new BufferedReader(new StringReader(printableOutput)).lines()) {
+ lines.forEach(line -> {
+ sb.append("\n ").append(line);
+ });
+ }
+ Log.verbose(sb.toString());
+ }
+
+ result.exitCode().ifPresentOrElse(exitCode -> {
+ Log.verbose("Returned: " + exitCode + "\n");
+ }, () -> {
+ Log.verbose("Aborted: timed-out" + "\n");
+ });
+ }
+
+ private static final class PrintableOutputBuilder {
+
+ PrintableOutputBuilder(CommandOutputControl coc) {
+ coc.dumpOutput(true);
+ charset = coc.charset();
+ if (coc.isBinaryOutput()) {
+ // Assume binary output goes into stdout and text error messages go into stderr, so keep them separated.
+ sinks = new ByteArrayOutputStream[2];
+ sinks[0] = new ByteArrayOutputStream();
+ sinks[1] = new ByteArrayOutputStream();
+ coc.dumpStdout(new PrintStream(sinks[0], false, charset))
+ .dumpStderr(new PrintStream(sinks[1], false, charset));
+ } else {
+ sinks = new ByteArrayOutputStream[1];
+ sinks[0] = new ByteArrayOutputStream();
+ var ps = new PrintStream(sinks[0], false, charset);
+ // Redirect stderr in stdout.
+ coc.dumpStdout(ps).dumpStderr(ps);
}
}
- }
- static Executor of(String... cmdline) {
- return new Executor().setCommandLine(cmdline);
- }
-
- static Executor of(ProcessBuilder pb) {
- return new Executor().setProcessBuilder(pb);
- }
-
- private static String createLogMessage(ProcessBuilder pb, boolean quiet) {
- StringBuilder sb = new StringBuilder();
- sb.append((quiet) ? pb.command().get(0) : pb.command());
- if (pb.directory() != null) {
- sb.append(String.format(" in %s", pb.directory().getAbsolutePath()));
+ String create() {
+ if (isBinaryOutput()) {
+ // In case of binary output:
+ // - Convert binary stdout to text using ISO-8859-1 encoding and
+ // replace non-printable characters with the question mark symbol (?).
+ // - Convert binary stderr to text using designated encoding (assume stderr is always a character stream).
+ // - Merge text stdout and stderr into a single string;
+ // stderr first, stdout follows, with the aim to present user error messages first.
+ var sb = new StringBuilder();
+ var stdout = sinks[0].toString(StandardCharsets.ISO_8859_1).replaceAll("[^\\p{Print}\\p{Space}]", "?");
+ return sb.append(sinks[1].toString(charset)).append(stdout).toString();
+ } else {
+ return sinks[0].toString(charset);
+ }
}
- return sb.toString();
+
+ private boolean isBinaryOutput() {
+ return sinks.length == 2;
+ }
+
+ private final ByteArrayOutputStream sinks[];
+ private final Charset charset;
}
- public static final int INFINITE_TIMEOUT = -1;
-
- private ProcessBuilder pb;
- private boolean saveOutput;
- private boolean writeOutputToFile;
+ private final CommandOutputControl commandOutputControl;
private boolean quietCommand;
- private long timeout = INFINITE_TIMEOUT;
- private List output;
- private Consumer> outputConsumer;
+ private final List args;
+ private ProcessBuilder processBuilder;
+ private ToolProvider toolProvider;
+ private Duration timeout;
+ private UnaryOperator mapper;
}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ExecutorFactory.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ExecutorFactory.java
new file mode 100644
index 00000000000..ce703358b82
--- /dev/null
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ExecutorFactory.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2026, 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.
+ */
+package jdk.jpackage.internal;
+
+@FunctionalInterface
+interface ExecutorFactory {
+
+ Executor executor();
+
+ static final ExecutorFactory DEFAULT = Executor::new;
+}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java
index 6b74cab4e65..cccd05792d6 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, 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
@@ -46,6 +46,7 @@ import static jdk.jpackage.internal.cli.StandardOption.RESOURCE_DIR;
import static jdk.jpackage.internal.cli.StandardOption.VENDOR;
import java.nio.file.Path;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -60,6 +61,7 @@ import jdk.jpackage.internal.model.Launcher;
import jdk.jpackage.internal.model.LauncherModularStartupInfo;
import jdk.jpackage.internal.model.PackageType;
import jdk.jpackage.internal.model.RuntimeLayout;
+import jdk.jpackage.internal.util.RootedPath;
final class FromOptions {
@@ -168,8 +170,15 @@ final class FromOptions {
APP_VERSION.ifPresentIn(options, appBuilder::version);
VENDOR.ifPresentIn(options, appBuilder::vendor);
COPYRIGHT.ifPresentIn(options, appBuilder::copyright);
- INPUT.ifPresentIn(options, appBuilder::srcDir);
- APP_CONTENT.ifPresentIn(options, appBuilder::contentDirs);
+ INPUT.ifPresentIn(options, appBuilder::appDirSources);
+ APP_CONTENT.findIn(options).map((List> v) -> {
+ // Reverse the order of content sources.
+ // If there are multiple source files for the same
+ // destination file, only the first will be used.
+ // Reversing the order of content sources makes it use the last file
+ // from the original list of source files for the given destination file.
+ return v.reversed().stream().flatMap(Collection::stream).toList();
+ }).ifPresent(appBuilder::contentDirSources);
if (isRuntimeInstaller) {
appBuilder.appImageLayout(runtimeLayout);
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Globals.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Globals.java
new file mode 100644
index 00000000000..91ae37870a5
--- /dev/null
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Globals.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2026, 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.
+ */
+package jdk.jpackage.internal;
+
+import java.io.PrintWriter;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+public final class Globals {
+
+ private Globals() {
+ }
+
+ Globals objectFactory(ObjectFactory v) {
+ checkMutable();
+ objectFactory = Optional.ofNullable(v).orElse(ObjectFactory.DEFAULT);
+ return this;
+ }
+
+ ObjectFactory objectFactory() {
+ return objectFactory;
+ }
+
+ Globals executorFactory(ExecutorFactory v) {
+ return objectFactory(ObjectFactory.build(objectFactory).executorFactory(v).create());
+ }
+
+ Log.Logger logger() {
+ return logger;
+ }
+
+ public void loggerOutputStreams(PrintWriter out, PrintWriter err) {
+ logger.setPrintWriter(out, err);
+ }
+
+ public void loggerVerbose() {
+ logger.setVerbose();
+ }
+
+ public static int main(Supplier mainBody) {
+ if (INSTANCE.isBound()) {
+ return mainBody.get();
+ } else {
+ return ScopedValue.where(INSTANCE, new Globals()).call(mainBody::get);
+ }
+ }
+
+ public static Globals instance() {
+ return INSTANCE.orElse(DEFAULT);
+ }
+
+ private void checkMutable() {
+ if (this == DEFAULT) {
+ throw new UnsupportedOperationException("Can't modify immutable instance");
+ }
+ }
+
+ private ObjectFactory objectFactory = ObjectFactory.DEFAULT;
+ private final Log.Logger logger = new Log.Logger();
+
+ private static final ScopedValue INSTANCE = ScopedValue.newInstance();
+ private static final Globals DEFAULT = new Globals();
+}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java
index aac113d7777..08cf0c10982 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2026, 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
@@ -26,12 +26,9 @@
package jdk.jpackage.internal;
import java.io.IOException;
-import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
-import java.util.ArrayList;
-import java.util.List;
import jdk.jpackage.internal.model.JPackageException;
/**
@@ -50,46 +47,6 @@ final class IOUtils {
StandardCopyOption.COPY_ATTRIBUTES);
}
- public static void exec(ProcessBuilder pb)
- throws IOException {
- exec(pb, false, null, false, Executor.INFINITE_TIMEOUT);
- }
-
- // timeout in seconds. -1 will be return if process timeouts.
- public static void exec(ProcessBuilder pb, long timeout)
- throws IOException {
- exec(pb, false, null, false, timeout);
- }
-
- static void exec(ProcessBuilder pb, boolean testForPresenceOnly,
- PrintStream consumer, boolean writeOutputToFile, long timeout)
- throws IOException {
- exec(pb, testForPresenceOnly, consumer, writeOutputToFile,
- timeout, false);
- }
-
- static void exec(ProcessBuilder pb, boolean testForPresenceOnly,
- PrintStream consumer, boolean writeOutputToFile,
- long timeout, boolean quiet) throws IOException {
- List output = new ArrayList<>();
- Executor exec = Executor.of(pb)
- .setWriteOutputToFile(writeOutputToFile)
- .setTimeout(timeout)
- .setQuiet(quiet)
- .setOutputConsumer(lines -> {
- lines.forEach(output::add);
- if (consumer != null) {
- output.forEach(consumer::println);
- }
- });
-
- if (testForPresenceOnly) {
- exec.execute();
- } else {
- exec.executeExpectSuccess();
- }
- }
-
static void writableOutputDir(Path outdir) {
if (!Files.isDirectory(outdir)) {
try {
@@ -103,15 +60,4 @@ final class IOUtils {
throw new JPackageException(I18N.format("error.cannot-write-to-output-dir", outdir.toAbsolutePath()));
}
}
-
- public static long getPID(Process p) {
- try {
- return p.pid();
- } catch (UnsupportedOperationException ex) {
- Log.verbose(ex); // Just log exception and ignore it. This method
- // is used for verbose output, so not a problem
- // if unsupported.
- return -1;
- }
- }
}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java
index 37f166a4e7c..f960f8dc2bf 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2026, 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
@@ -22,13 +22,14 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
+
package jdk.jpackage.internal;
+
import static jdk.jpackage.internal.model.RuntimeBuilder.getDefaultModulePath;
+import static jdk.jpackage.internal.util.function.ThrowingRunnable.toRunnable;
import java.io.File;
import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
@@ -50,7 +51,6 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.internal.module.ModulePath;
import jdk.jpackage.internal.model.AppImageLayout;
-import jdk.jpackage.internal.model.JPackageException;
import jdk.jpackage.internal.model.LauncherModularStartupInfo;
import jdk.jpackage.internal.model.LauncherStartupInfo;
import jdk.jpackage.internal.model.RuntimeBuilder;
@@ -58,27 +58,15 @@ import jdk.jpackage.internal.model.RuntimeBuilder;
final class JLinkRuntimeBuilder implements RuntimeBuilder {
private JLinkRuntimeBuilder(List jlinkCmdLine) {
- this.jlinkCmdLine = jlinkCmdLine;
+ this.jlinkCmdLine = Objects.requireNonNull(jlinkCmdLine);
}
@Override
public void create(AppImageLayout appImageLayout) {
- var args = new ArrayList();
- args.add("--output");
- args.add(appImageLayout.runtimeDirectory().toString());
- args.addAll(jlinkCmdLine);
-
- StringWriter writer = new StringWriter();
- PrintWriter pw = new PrintWriter(writer);
-
- int retVal = LazyLoad.JLINK_TOOL.run(pw, pw, args.toArray(String[]::new));
- String jlinkOut = writer.toString();
-
- args.add(0, "jlink");
- Log.verbose(args, List.of(jlinkOut), retVal, -1);
- if (retVal != 0) {
- throw new JPackageException(I18N.format("error.jlink.failed", jlinkOut));
- }
+ toRunnable(Executor.of()
+ .toolProvider(LazyLoad.JLINK_TOOL)
+ .args("--output", appImageLayout.runtimeDirectory().toString())
+ .args(jlinkCmdLine)::executeExpectSuccess).run();
}
@Override
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherBuilder.java
index e3720e0a776..5658760b4d6 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherBuilder.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherBuilder.java
@@ -50,13 +50,16 @@ final class LauncherBuilder {
final var fa = createFileAssociations(faSources, Optional.ofNullable(faTraits).orElse(DEFAULT_FA_TRAITS));
- Objects.requireNonNull(defaultIconResourceName);
-
final var nonNullName = deriveNonNullName();
- return new Stub(nonNullName, Optional.ofNullable(startupInfo), fa,
- isService, Optional.ofNullable(description).orElse(nonNullName),
- Optional.ofNullable(icon), defaultIconResourceName,
+ return new Stub(
+ nonNullName,
+ Optional.ofNullable(startupInfo),
+ fa,
+ isService,
+ Optional.ofNullable(description).orElse(nonNullName),
+ Optional.ofNullable(icon),
+ Optional.ofNullable(defaultIconResourceName).orElseGet(LauncherBuilder::defaultIconResourceName),
Optional.ofNullable(extraAppImageFileData).orElseGet(Map::of));
}
@@ -170,6 +173,23 @@ final class LauncherBuilder {
return FileAssociationGroup.flatMap(groups.stream()).toList();
}
+ private static String defaultIconResourceName() {
+ switch (OperatingSystem.current()) {
+ case WINDOWS -> {
+ return "JavaApp.ico";
+ }
+ case LINUX -> {
+ return "JavaApp.png";
+ }
+ case MACOS -> {
+ return "JavaApp.icns";
+ }
+ default -> {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
private String name;
private LauncherStartupInfo startupInfo;
private List faSources = List.of();
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java
index 0749cc48b9b..1ba384dba2d 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, 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
@@ -45,17 +45,18 @@ import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.stream.IntStream;
-import jdk.internal.util.OperatingSystem;
import jdk.jpackage.internal.FileAssociationGroup.FileAssociationException;
import jdk.jpackage.internal.FileAssociationGroup.FileAssociationNoExtensionsException;
import jdk.jpackage.internal.FileAssociationGroup.FileAssociationNoMimesException;
import jdk.jpackage.internal.cli.Options;
import jdk.jpackage.internal.cli.StandardFaOption;
import jdk.jpackage.internal.model.CustomLauncherIcon;
+import jdk.jpackage.internal.model.JPackageException;
import jdk.jpackage.internal.model.DefaultLauncherIcon;
import jdk.jpackage.internal.model.FileAssociation;
import jdk.jpackage.internal.model.Launcher;
import jdk.jpackage.internal.model.LauncherIcon;
+import jdk.jpackage.internal.util.RootedPath;
final class LauncherFromOptions {
@@ -82,7 +83,7 @@ final class LauncherFromOptions {
}
Launcher create(Options options) {
- final var builder = new LauncherBuilder().defaultIconResourceName(defaultIconResourceName());
+ final var builder = new LauncherBuilder();
DESCRIPTION.ifPresentIn(options, builder::description);
builder.icon(toLauncherIcon(ICON.findIn(options).orElse(null)));
@@ -92,7 +93,9 @@ final class LauncherFromOptions {
if (PREDEFINED_APP_IMAGE.findIn(options).isEmpty()) {
final var startupInfoBuilder = new LauncherStartupInfoBuilder();
- INPUT.ifPresentIn(options, startupInfoBuilder::inputDir);
+ INPUT.findIn(options).flatMap(v -> {
+ return v.stream().findAny().map(RootedPath::root);
+ }).ifPresent(startupInfoBuilder::inputDir);
ARGUMENTS.ifPresentIn(options, startupInfoBuilder::defaultParameters);
JAVA_OPTIONS.ifPresentIn(options, startupInfoBuilder::javaOptions);
MAIN_JAR.ifPresentIn(options, startupInfoBuilder::mainJar);
@@ -131,8 +134,7 @@ final class LauncherFromOptions {
.advice("error.no-content-types-for-file-association.advice", faID)
.create();
} catch (FileAssociationNoExtensionsException ex) {
- // TODO: Must do something about this condition!
- throw new AssertionError();
+ throw new JPackageException(I18N.format("error.no-extensions-for-file-association", faID));
} catch (FileAssociationException ex) {
// Should never happen
throw new UnsupportedOperationException(ex);
@@ -167,23 +169,6 @@ final class LauncherFromOptions {
}
}
- private static String defaultIconResourceName() {
- switch (OperatingSystem.current()) {
- case WINDOWS -> {
- return "JavaApp.ico";
- }
- case LINUX -> {
- return "JavaApp.png";
- }
- case MACOS -> {
- return "JavaApp.icns";
- }
- default -> {
- throw new UnsupportedOperationException();
- }
- }
- }
-
private BiConsumer faGroupBuilderMutator;
private BiFunction faMapper;
}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Log.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Log.java
index b14b4ab22ca..5c27ef67500 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Log.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Log.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2026, 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
@@ -28,7 +28,6 @@ package jdk.jpackage.internal;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
-import java.util.List;
/**
* Log
@@ -62,16 +61,6 @@ public class Log {
this.err = err;
}
- public void flush() {
- if (out != null) {
- out.flush();
- }
-
- if (err != null) {
- err.flush();
- }
- }
-
public void info(String msg) {
if (out != null) {
out.println(msg);
@@ -105,29 +94,6 @@ public class Log {
}
}
- public void verbose(List strings,
- List output, int returnCode, long pid) {
- if (verbose) {
- StringBuilder sb = new StringBuilder();
- sb.append("Command [PID: ");
- sb.append(pid);
- sb.append("]:\n ");
-
- for (String s : strings) {
- sb.append(" " + s);
- }
- verbose(sb.toString());
- if (output != null && !output.isEmpty()) {
- sb = new StringBuilder("Output:");
- for (String s : output) {
- sb.append("\n " + s);
- }
- verbose(sb.toString());
- }
- verbose("Returned: " + returnCode + "\n");
- }
- }
-
private String addTimestamp(String msg) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
Date time = new Date(System.currentTimeMillis());
@@ -135,51 +101,27 @@ public class Log {
}
}
- private static final InheritableThreadLocal instance =
- new InheritableThreadLocal() {
- @Override protected Logger initialValue() {
- return new Logger();
- }
- };
-
- public static void setPrintWriter (PrintWriter out, PrintWriter err) {
- instance.get().setPrintWriter(out, err);
- }
-
- public static void flush() {
- instance.get().flush();
- }
-
public static void info(String msg) {
- instance.get().info(msg);
+ Globals.instance().logger().info(msg);
}
public static void fatalError(String msg) {
- instance.get().fatalError(msg);
+ Globals.instance().logger().fatalError(msg);
}
public static void error(String msg) {
- instance.get().error(msg);
- }
-
- public static void setVerbose() {
- instance.get().setVerbose();
+ Globals.instance().logger().error(msg);
}
public static boolean isVerbose() {
- return instance.get().isVerbose();
+ return Globals.instance().logger().isVerbose();
}
public static void verbose(String msg) {
- instance.get().verbose(msg);
+ Globals.instance().logger().verbose(msg);
}
public static void verbose(Throwable t) {
- instance.get().verbose(t);
- }
-
- public static void verbose(List strings, List out,
- int ret, long pid) {
- instance.get().verbose(strings, out, ret, pid);
+ Globals.instance().logger().verbose(t);
}
}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ObjectFactory.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ObjectFactory.java
new file mode 100644
index 00000000000..f1a83eb9eab
--- /dev/null
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ObjectFactory.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2026, 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.
+ */
+package jdk.jpackage.internal;
+
+import java.util.Objects;
+import java.util.Optional;
+import jdk.jpackage.internal.util.CompositeProxy;
+
+interface ObjectFactory extends ExecutorFactory, RetryExecutorFactory {
+
+ static ObjectFactory.Builder build() {
+ return new Builder();
+ }
+
+ static ObjectFactory.Builder build(ObjectFactory from) {
+ return build().initFrom(from);
+ }
+
+ static final class Builder {
+ private Builder() {
+ }
+
+ ObjectFactory create() {
+ return CompositeProxy.build().invokeTunnel(CompositeProxyTunnel.INSTANCE).create(
+ ObjectFactory.class,
+ Optional.ofNullable(executorFactory).orElse(ExecutorFactory.DEFAULT),
+ Optional.ofNullable(retryExecutorFactory).orElse(RetryExecutorFactory.DEFAULT));
+ }
+
+ Builder initFrom(ObjectFactory of) {
+ Objects.requireNonNull(of);
+ return executorFactory(of).retryExecutorFactory(of);
+ }
+
+ Builder executorFactory(ExecutorFactory v) {
+ executorFactory = v;
+ return this;
+ }
+
+ Builder retryExecutorFactory(RetryExecutorFactory v) {
+ retryExecutorFactory = v;
+ return this;
+ }
+
+ private ExecutorFactory executorFactory;
+ private RetryExecutorFactory retryExecutorFactory;
+ }
+
+ static final ObjectFactory DEFAULT = build().create();
+}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java
index a2750fee260..315e3bce0f1 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, 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
@@ -29,9 +29,9 @@ import static java.util.stream.Collectors.toMap;
import static jdk.jpackage.internal.model.AppImageLayout.toPathGroup;
import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -39,12 +39,16 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
import java.util.function.Predicate;
+import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import jdk.jpackage.internal.model.AppImageLayout;
import jdk.jpackage.internal.model.Application;
import jdk.jpackage.internal.model.ApplicationLayout;
+import jdk.jpackage.internal.model.JPackageException;
import jdk.jpackage.internal.model.Package;
import jdk.jpackage.internal.pipeline.DirectedEdge;
import jdk.jpackage.internal.pipeline.FixedDAG;
@@ -97,7 +101,7 @@ final class PackagingPipeline {
/**
* The way to access packaging build environment before building a package in a pipeline.
*/
- interface StartupParameters {
+ sealed interface StartupParameters {
BuildEnv packagingEnv();
}
@@ -127,6 +131,7 @@ final class PackagingPipeline {
enum PackageTaskID implements TaskID {
RUN_POST_IMAGE_USER_SCRIPT,
CREATE_CONFIG_FILES,
+ DELETE_OLD_PACKAGE_FILE,
CREATE_PACKAGE_FILE
}
@@ -183,9 +188,11 @@ final class PackagingPipeline {
void execute() throws IOException;
}
- record TaskConfig(Optional action) {
+ record TaskConfig(Optional action, Optional beforeAction, Optional afterAction) {
TaskConfig {
Objects.requireNonNull(action);
+ Objects.requireNonNull(beforeAction);
+ Objects.requireNonNull(afterAction);
}
}
@@ -196,47 +203,64 @@ final class PackagingPipeline {
final class TaskBuilder extends TaskSpecBuilder {
- private TaskBuilder(TaskID id) {
- super(id);
- }
-
- private TaskBuilder(TaskID id, TaskConfig config) {
- this(id);
- config.action().ifPresent(this::setAction);
- }
-
- private TaskBuilder setAction(TaskAction v) {
- action = v;
- return this;
- }
-
TaskBuilder noaction() {
- action = null;
- return this;
+ return setAction(ActionRole.WORKLOAD, null);
}
TaskBuilder applicationAction(ApplicationImageTaskAction action) {
- return setAction(action);
+ return applicationAction(ActionRole.WORKLOAD, action);
}
TaskBuilder appImageAction(AppImageTaskAction action) {
- return setAction(action);
+ return appImageAction(ActionRole.WORKLOAD, action);
}
TaskBuilder copyAction(CopyAppImageTaskAction action) {
- return setAction(action);
+ return copyAction(ActionRole.WORKLOAD, action);
}
TaskBuilder packageAction(PackageTaskAction action) {
- return setAction(action);
+ return packageAction(ActionRole.WORKLOAD, action);
}
TaskBuilder action(NoArgTaskAction action) {
- return setAction(action);
+ return action(ActionRole.WORKLOAD, action);
+ }
+
+ TaskBuilder logAppImageActionBegin(String keyId, Function, Object[]> formatArgsSupplier) {
+ return logAppImageAction(ActionRole.BEFORE, keyId, formatArgsSupplier);
+ }
+
+ TaskBuilder logAppImageActionEnd(String keyId, Function, Object[]> formatArgsSupplier) {
+ return logAppImageAction(ActionRole.AFTER, keyId, formatArgsSupplier);
+ }
+
+ TaskBuilder logPackageActionBegin(String keyId, Function, Object[]> argsSupplier) {
+ return logPackageAction(ActionRole.BEFORE, keyId, argsSupplier);
+ }
+
+ TaskBuilder logPackageActionEnd(String keyId, Function, Object[]> argsSupplier) {
+ return logPackageAction(ActionRole.AFTER, keyId, argsSupplier);
+ }
+
+ TaskBuilder logActionBegin(String keyId, Supplier formatArgsSupplier) {
+ return logAction(ActionRole.BEFORE, keyId, formatArgsSupplier);
+ }
+
+ TaskBuilder logActionBegin(String keyId, Object... formatArgsSupplier) {
+ return logAction(ActionRole.BEFORE, keyId, () -> formatArgsSupplier);
+ }
+
+ TaskBuilder logActionEnd(String keyId, Supplier formatArgsSupplier) {
+ return logAction(ActionRole.AFTER, keyId, formatArgsSupplier);
+ }
+
+ TaskBuilder logActionEnd(String keyId, Object... formatArgsSupplier) {
+ return logAction(ActionRole.AFTER, keyId, () -> formatArgsSupplier);
}
boolean hasAction() {
- return action != null;
+ return workloadAction != null;
}
@Override
@@ -272,13 +296,109 @@ final class PackagingPipeline {
}
Builder add() {
- final var config = new TaskConfig(Optional.ofNullable(action));
+ final var config = new TaskConfig(
+ Optional.ofNullable(workloadAction),
+ Optional.ofNullable(beforeAction),
+ Optional.ofNullable(afterAction));
taskConfig.put(task(), config);
createLinks().forEach(Builder.this::linkTasks);
return Builder.this;
}
- private TaskAction action;
+
+ private enum ActionRole {
+ WORKLOAD(TaskBuilder::setWorkloadAction),
+ BEFORE(TaskBuilder::setBeforeAction),
+ AFTER(TaskBuilder::setAfterAction),
+ ;
+
+ ActionRole(BiConsumer actionSetter) {
+ this.actionSetter = Objects.requireNonNull(actionSetter);
+ }
+
+ TaskBuilder setAction(TaskBuilder taskBuilder, TaskAction action) {
+ actionSetter.accept(taskBuilder, action);
+ return taskBuilder;
+ }
+
+ private final BiConsumer actionSetter;
+ }
+
+
+ private TaskBuilder(TaskID id) {
+ super(id);
+ }
+
+ private TaskBuilder(TaskID id, TaskConfig config) {
+ this(id);
+ config.action().ifPresent(this::setWorkloadAction);
+ config.beforeAction().ifPresent(this::setBeforeAction);
+ config.afterAction().ifPresent(this::setAfterAction);
+ }
+
+ private TaskBuilder setAction(ActionRole role, TaskAction v) {
+ return role.setAction(this, v);
+ }
+
+ private TaskBuilder setWorkloadAction(TaskAction v) {
+ workloadAction = v;
+ return this;
+ }
+
+ private TaskBuilder setBeforeAction(TaskAction v) {
+ beforeAction = v;
+ return this;
+ }
+
+ private TaskBuilder setAfterAction(TaskAction v) {
+ afterAction = v;
+ return this;
+ }
+
+ private TaskBuilder applicationAction(ActionRole role, ApplicationImageTaskAction action) {
+ return setAction(role, action);
+ }
+
+ private TaskBuilder appImageAction(ActionRole role, AppImageTaskAction action) {
+ return setAction(role, action);
+ }
+
+ private TaskBuilder copyAction(ActionRole role, CopyAppImageTaskAction action) {
+ return setAction(role, action);
+ }
+
+ private TaskBuilder packageAction(ActionRole role, PackageTaskAction action) {
+ return setAction(role, action);
+ }
+
+ private TaskBuilder action(ActionRole role, NoArgTaskAction action) {
+ return setAction(role, action);
+ }
+
+ private TaskBuilder logAppImageAction(ActionRole role, String keyId, Function, Object[]> formatArgsSupplier) {
+ Objects.requireNonNull(keyId);
+ return appImageAction(role, (AppImageBuildEnv env) -> {
+ Log.verbose(I18N.format(keyId, formatArgsSupplier.apply(env)));
+ });
+ }
+
+ private TaskBuilder logPackageAction(ActionRole role, String keyId, Function, Object[]> formatArgsSupplier) {
+ Objects.requireNonNull(keyId);
+ return packageAction(role, (PackageBuildEnv env) -> {
+ Log.verbose(I18N.format(keyId, formatArgsSupplier.apply(env)));
+ });
+ }
+
+ private TaskBuilder logAction(ActionRole role, String keyId, Supplier formatArgsSupplier) {
+ Objects.requireNonNull(keyId);
+ return action(role, () -> {
+ Log.verbose(I18N.format(keyId, formatArgsSupplier.get()));
+ });
+ }
+
+ private TaskAction workloadAction;
+ private TaskAction beforeAction;
+ private TaskAction afterAction;
}
Builder linkTasks(DirectedEdge edge) {
@@ -294,7 +414,11 @@ final class PackagingPipeline {
}
TaskBuilder task(TaskID id) {
- return new TaskBuilder(id);
+ return Optional.ofNullable(taskConfig.get(id)).map(taskConfig -> {
+ return new TaskBuilder(id, taskConfig);
+ }).orElseGet(() -> {
+ return new TaskBuilder(id);
+ });
}
Stream configuredTasks() {
@@ -303,12 +427,6 @@ final class PackagingPipeline {
});
}
- Builder excludeDirFromCopying(Path path) {
- Objects.requireNonNull(path);
- excludeCopyDirs.add(path);
- return this;
- }
-
Builder contextMapper(UnaryOperator v) {
contextMapper = v;
return this;
@@ -331,7 +449,6 @@ final class PackagingPipeline {
}
private final FixedDAG.Builder taskGraphBuilder = FixedDAG.build();
- private final List excludeCopyDirs = new ArrayList<>();
private final Map taskConfig = new HashMap<>();
private UnaryOperator contextMapper;
private FixedDAG taskGraphSnapshot;
@@ -365,7 +482,7 @@ final class PackagingPipeline {
builder.task(BuildApplicationTaskID.CONTENT)
.addDependent(BuildApplicationTaskID.APP_IMAGE_FILE)
- .applicationAction(ApplicationImageUtils.createCopyContentAction(() -> builder.excludeCopyDirs)).add();
+ .applicationAction(ApplicationImageUtils.createCopyContentAction()).add();
return builder;
}
@@ -392,6 +509,8 @@ final class PackagingPipeline {
builder.task(PackageTaskID.CREATE_PACKAGE_FILE)
.addDependent(PrimaryTaskID.PACKAGE)
+ .logActionBegin("message.create-package")
+ .logActionEnd("message.package-created")
.add();
builder.task(PrimaryTaskID.PACKAGE).add();
@@ -425,6 +544,17 @@ final class PackagingPipeline {
.run(env.env(), env.pkg().app().name());
}
+ static void deleteOutputBundle(PackageBuildEnv env) throws IOException {
+
+ var outputBundle = env.outputDir().resolve(env.pkg().packageFileNameWithSuffix());
+
+ try {
+ Files.deleteIfExists(outputBundle);
+ } catch (IOException ex) {
+ throw new JPackageException(I18N.format("error.output-bundle-cannot-be-overwritten", outputBundle.toAbsolutePath()), ex);
+ }
+ }
+
private PackagingPipeline(FixedDAG taskGraph, Map taskConfig,
UnaryOperator contextMapper) {
this.taskGraph = Objects.requireNonNull(taskGraph);
@@ -508,7 +638,7 @@ final class PackagingPipeline {
try {
builder.create().call();
} catch (Exception ex) {
- throw ExceptionBox.rethrowUnchecked(ex);
+ throw ExceptionBox.toUnchecked(ex);
}
}
@@ -632,7 +762,7 @@ final class PackagingPipeline {
final var accepted = withAction && context.test(id);
if (TRACE_TASK_ACTION) {
- var sb = new StringBuffer();
+ var sb = new StringBuilder();
sb.append("Execute task=[").append(id).append("]: ");
if (!withAction) {
sb.append("no action");
@@ -645,7 +775,13 @@ final class PackagingPipeline {
}
if (accepted) {
+ if (config.beforeAction.isPresent()) {
+ context.execute(config.beforeAction.orElseThrow());
+ }
context.execute(config.action.orElseThrow());
+ if (config.afterAction.isPresent()) {
+ context.execute(config.afterAction.orElseThrow());
+ }
}
return null;
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RetryExecutor.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RetryExecutor.java
deleted file mode 100644
index f806217e8f9..00000000000
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RetryExecutor.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (c) 2020, 2023, 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.
- */
-package jdk.jpackage.internal;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-public final class RetryExecutor {
- public RetryExecutor() {
- setMaxAttemptsCount(5);
- setAttemptTimeoutMillis(2 * 1000);
- setWriteOutputToFile(false);
- }
-
- public RetryExecutor setMaxAttemptsCount(int v) {
- attempts = v;
- return this;
- }
-
- public RetryExecutor setAttemptTimeoutMillis(int v) {
- timeoutMillis = v;
- return this;
- }
-
- public RetryExecutor saveOutput(boolean v) {
- saveOutput = v;
- return this;
- }
-
- public List getOutput() {
- return output;
- }
-
- public RetryExecutor setWriteOutputToFile(boolean v) {
- writeOutputToFile = v;
- return this;
- }
-
- public RetryExecutor setExecutorInitializer(Consumer v) {
- executorInitializer = v;
- return this;
- }
-
- public void abort() {
- aborted = true;
- }
-
- public boolean isAborted() {
- return aborted;
- }
-
- static RetryExecutor retryOnKnownErrorMessage(String v) {
- RetryExecutor result = new RetryExecutor();
- return result.setExecutorInitializer(exec -> {
- exec.setOutputConsumer(output -> {
- if (!output.anyMatch(v::equals)) {
- result.abort();
- }
- });
- });
- }
-
- public void execute(String cmdline[]) throws IOException {
- executeLoop(() ->
- Executor.of(cmdline).setWriteOutputToFile(writeOutputToFile));
- }
-
- public void execute(ProcessBuilder pb) throws IOException {
- executeLoop(() ->
- Executor.of(pb).setWriteOutputToFile(writeOutputToFile));
- }
-
- private void executeLoop(Supplier execSupplier) throws IOException {
- aborted = false;
- for (;;) {
- if (aborted) {
- break;
- }
-
- try {
- Executor exec = execSupplier.get().saveOutput(saveOutput);
- if (executorInitializer != null) {
- executorInitializer.accept(exec);
- }
- exec.executeExpectSuccess();
- if (saveOutput) {
- output = exec.getOutput();
- }
- break;
- } catch (IOException ex) {
- if (aborted || (--attempts) <= 0) {
- throw ex;
- }
- }
-
- try {
- Thread.sleep(timeoutMillis);
- } catch (InterruptedException ex) {
- Log.verbose(ex);
- throw new RuntimeException(ex);
- }
- }
- }
-
- private Consumer executorInitializer;
- private boolean aborted;
- private int attempts;
- private int timeoutMillis;
- private boolean saveOutput;
- private List output;
- private boolean writeOutputToFile;
-}
diff --git a/src/java.desktop/share/native/libjsound/Platform.c b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RetryExecutorFactory.java
similarity index 69%
rename from src/java.desktop/share/native/libjsound/Platform.c
rename to src/jdk.jpackage/share/classes/jdk/jpackage/internal/RetryExecutorFactory.java
index ecb389c89b7..3efb522abd4 100644
--- a/src/java.desktop/share/native/libjsound/Platform.c
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RetryExecutorFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2026, 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
@@ -22,22 +22,14 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
+package jdk.jpackage.internal;
+import jdk.jpackage.internal.util.RetryExecutor;
-#include "Utilities.h"
-// Platform.java includes
-#include "com_sun_media_sound_Platform.h"
+@FunctionalInterface
+interface RetryExecutorFactory {
-/*
- * Declare library specific JNI_Onload entry if static build
- */
-DEF_STATIC_JNI_OnLoad
+ RetryExecutor retryExecutor(Class extends E> exceptionType);
-/*
- * Class: com_sun_media_sound_Platform
- * Method: nIsBigEndian
- * Signature: ()Z
- */
-JNIEXPORT jboolean JNICALL Java_com_sun_media_sound_Platform_nIsBigEndian(JNIEnv *env, jclass clss) {
- return UTIL_IsBigEndianPlatform();
+ static final RetryExecutorFactory DEFAULT = RetryExecutor::new;
}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/SystemEnvironment.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/SystemEnvironment.java
index de98e97c922..5d9a1d6d147 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/SystemEnvironment.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/SystemEnvironment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, 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
@@ -24,5 +24,5 @@
*/
package jdk.jpackage.internal;
-public interface SystemEnvironment {
+interface SystemEnvironment {
}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java
index 13e87d5cfa6..9440aff3a53 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, 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
@@ -121,32 +121,32 @@ final class ToolValidator {
cmdline.addAll(args);
}
- boolean canUseTool[] = new boolean[1];
+ boolean canUseTool = false;
if (minimalVersion == null) {
// No version check.
- canUseTool[0] = true;
+ canUseTool = true;
}
- String[] version = new String[1];
+ String version = null;
try {
- Executor.of(cmdline.toArray(String[]::new)).setQuiet(true).setOutputConsumer(lines -> {
- if (versionParser != null && minimalVersion != null) {
- version[0] = versionParser.apply(lines);
- if (version[0] != null && minimalVersion.compareTo(version[0]) <= 0) {
- canUseTool[0] = true;
- }
+ var result = Executor.of(cmdline).setQuiet(true).saveOutput().execute();
+ var lines = result.content();
+ if (versionParser != null && minimalVersion != null) {
+ version = versionParser.apply(lines.stream());
+ if (version != null && minimalVersion.compareTo(version) <= 0) {
+ canUseTool = true;
}
- }).execute();
+ }
} catch (IOException e) {
return new ConfigException(I18N.format("error.tool-error", toolPath, e.getMessage()), null, e);
}
- if (canUseTool[0]) {
+ if (canUseTool) {
// All good. Tool can be used.
return null;
} else if (toolOldVersionErrorHandler != null) {
- return toolOldVersionErrorHandler.apply(toolPath, version[0]);
+ return toolOldVersionErrorHandler.apply(toolPath, version);
} else {
return new ConfigException(
I18N.format("error.tool-old-version", toolPath, minimalVersion),
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java
index 57b92471e4a..bff35874645 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, 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
@@ -27,6 +27,7 @@ package jdk.jpackage.internal.cli;
import static java.util.stream.Collectors.toUnmodifiableMap;
import static java.util.stream.Collectors.toUnmodifiableSet;
+import static jdk.jpackage.internal.cli.OptionValueConverter.convertString;
import java.lang.reflect.Array;
import java.util.ArrayList;
@@ -565,7 +566,7 @@ final class JOptSimpleOptionsBuilder {
final var converter = optionSpec.converter().orElseThrow();
final Result conversionResult = optionValue.map(v -> {
- return converter.convert(optionName(), StringToken.of(v));
+ return convertString(converter, optionName(), StringToken.of(v));
}).orElseGet(() -> {
return Result.ofValue(optionSpec.defaultOptionalValue().orElseThrow());
});
@@ -579,7 +580,7 @@ final class JOptSimpleOptionsBuilder {
final String str = getOptionValue(List.of(tokens), optionSpec.mergePolicy()).getFirst();
final String[] token = arrConverter.tokenize(str);
if (token.length == 1 && str.equals(token[0])) {
- final var singleTokenConversionResult = converter.convert(optionName(), StringToken.of(str));
+ final var singleTokenConversionResult = convertString(converter, optionName(), StringToken.of(str));
if (singleTokenConversionResult.hasValue()) {
return singleTokenConversionResult;
}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Main.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Main.java
index af51bc9fd98..562a0d2d3c1 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Main.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Main.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, 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
@@ -29,10 +29,12 @@ import static jdk.jpackage.internal.cli.StandardOption.HELP;
import static jdk.jpackage.internal.cli.StandardOption.VERBOSE;
import static jdk.jpackage.internal.cli.StandardOption.VERSION;
+import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
+import java.io.StringReader;
import java.io.UncheckedIOException;
import java.nio.file.NoSuchFileException;
import java.util.Collection;
@@ -45,9 +47,15 @@ import java.util.function.Supplier;
import java.util.spi.ToolProvider;
import jdk.internal.opt.CommandLine;
import jdk.internal.util.OperatingSystem;
+import jdk.jpackage.internal.Globals;
import jdk.jpackage.internal.Log;
import jdk.jpackage.internal.model.ConfigException;
+import jdk.jpackage.internal.model.ExecutableAttributesWithCapturedOutput;
import jdk.jpackage.internal.model.JPackageException;
+import jdk.jpackage.internal.model.SelfContainedException;
+import jdk.jpackage.internal.util.CommandOutputControl.UnexpectedExitCodeException;
+import jdk.jpackage.internal.util.CommandOutputControl.UnexpectedResultException;
+import jdk.jpackage.internal.util.Slot;
import jdk.jpackage.internal.util.function.ExceptionBox;
/**
@@ -55,7 +63,15 @@ import jdk.jpackage.internal.util.function.ExceptionBox;
*/
public final class Main {
- public static final class Provider implements ToolProvider {
+ public record Provider(Supplier bundlingEnvSupplier) implements ToolProvider {
+
+ public Provider {
+ Objects.requireNonNull(bundlingEnvSupplier);
+ }
+
+ public Provider() {
+ this(DefaultBundlingEnvironmentLoader.INSTANCE);
+ }
@Override
public String name() {
@@ -64,13 +80,13 @@ public final class Main {
@Override
public int run(PrintWriter out, PrintWriter err, String... args) {
- return Main.run(out, err, args);
+ return Main.run(bundlingEnvSupplier, out, err, args);
}
@Override
public int run(PrintStream out, PrintStream err, String... args) {
- PrintWriter outWriter = new PrintWriter(out, true);
- PrintWriter errWriter = new PrintWriter(err, true);
+ PrintWriter outWriter = toPrintWriter(out);
+ PrintWriter errWriter = toPrintWriter(err);
try {
try {
return run(outWriter, errWriter, args);
@@ -88,12 +104,28 @@ public final class Main {
}
public static void main(String... args) {
- var out = new PrintWriter(System.out, true);
- var err = new PrintWriter(System.err, true);
+ var out = toPrintWriter(System.out);
+ var err = toPrintWriter(System.err);
System.exit(run(out, err, args));
}
- public static int run(PrintWriter out, PrintWriter err, String... args) {
+ static int run(PrintWriter out, PrintWriter err, String... args) {
+ return run(DefaultBundlingEnvironmentLoader.INSTANCE, out, err, args);
+ }
+
+ static int run(Supplier bundlingEnvSupplier, PrintWriter out, PrintWriter err, String... args) {
+ return Globals.main(() -> {
+ return runWithGlobals(bundlingEnvSupplier, out, err, args);
+ });
+ }
+
+ private static int runWithGlobals(
+ Supplier bundlingEnvSupplier,
+ PrintWriter out,
+ PrintWriter err,
+ String... args) {
+
+ Objects.requireNonNull(bundlingEnvSupplier);
Objects.requireNonNull(args);
for (String arg : args) {
Objects.requireNonNull(arg);
@@ -101,24 +133,37 @@ public final class Main {
Objects.requireNonNull(out);
Objects.requireNonNull(err);
- Log.setPrintWriter(out, err);
+ Globals.instance().loggerOutputStreams(out, err);
+
+ final var runner = new Runner(t -> {
+ new ErrorReporter(_ -> {
+ t.printStackTrace(err);
+ }, Log::fatalError, Log.isVerbose()).reportError(t);
+ });
try {
- try {
- args = CommandLine.parse(args);
- } catch (FileNotFoundException|NoSuchFileException ex) {
- Log.fatalError(I18N.format("ERR_CannotParseOptions", ex.getMessage()));
- return 1;
- } catch (IOException ex) {
- throw ExceptionBox.rethrowUnchecked(ex);
+ var mappedArgs = Slot.createEmpty();
+
+ int preprocessStatus = runner.run(() -> {
+ try {
+ mappedArgs.set(CommandLine.parse(args));
+ return List.of();
+ } catch (FileNotFoundException | NoSuchFileException ex) {
+ return List.of(new JPackageException(I18N.format("ERR_CannotParseOptions", ex.getMessage()), ex));
+ } catch (IOException ex) {
+ return List.of(ex);
+ }
+ });
+
+ if (preprocessStatus != 0) {
+ return preprocessStatus;
}
- final var bundlingEnv = ServiceLoader.load(CliBundlingEnvironment.class,
- CliBundlingEnvironment.class.getClassLoader()).findFirst().orElseThrow();
+ final var bundlingEnv = bundlingEnvSupplier.get();
- final var parseResult = Utils.buildParser(OperatingSystem.current(), bundlingEnv).create().apply(args);
+ final var parseResult = Utils.buildParser(OperatingSystem.current(), bundlingEnv).create().apply(mappedArgs.get());
- return new Runner().run(() -> {
+ return runner.run(() -> {
final var parsedOptionsBuilder = parseResult.orElseThrow();
final var options = parsedOptionsBuilder.create();
@@ -140,7 +185,7 @@ public final class Main {
}
if (VERBOSE.containsIn(options)) {
- Log.setVerbose();
+ Globals.instance().loggerVerbose();
}
final var optionsProcessor = new OptionsProcessor(parsedOptionsBuilder, bundlingEnv);
@@ -164,13 +209,35 @@ public final class Main {
}
}
+ /*
+ * Exception (error) reporting:
+ *
+ * There are two types of exceptions to handle:
+ *
+ * 1. Exceptions explicitly thrown by jpackage code with localized,
+ * jpackage-specific error messages. These are usually instances of
+ * JPackageException.
+ *
+ * 2. Exceptions thrown by JDK code (for example, an NPE from Optional.of(...)).
+ * These should normally not occur or should be handled at the point
+ * where they arise. If they reach this level of exception handling,
+ * it indicates a flaw in jpackage’s internal logic.
+ *
+ * Always print stack traces for exceptions of type #2.
+ * Print stack traces for exceptions of type #1 only in verbose mode.
+ * Always print the messages for exceptions of any type.
+ */
- record ErrorReporter(Consumer stackTracePrinter, Consumer messagePrinter) {
+ record ErrorReporter(Consumer stackTracePrinter, Consumer messagePrinter, boolean verbose) {
ErrorReporter {
Objects.requireNonNull(stackTracePrinter);
Objects.requireNonNull(messagePrinter);
}
+ ErrorReporter(Consumer stackTracePrinter, Consumer messagePrinter) {
+ this(stackTracePrinter, messagePrinter, true);
+ }
+
void reportError(Throwable t) {
if (t instanceof ConfigException cfgEx) {
printError(cfgEx, Optional.ofNullable(cfgEx.getAdvice()));
@@ -178,53 +245,76 @@ public final class Main {
reportError(ex.getCause());
} else if (t instanceof UncheckedIOException ex) {
reportError(ex.getCause());
+ } else if (t instanceof UnexpectedResultException ex) {
+ printExternalCommandError(ex);
} else {
printError(t, Optional.empty());
}
}
- private void printError(Throwable t, Optional advice) {
- stackTracePrinter.accept(t);
+ private void printExternalCommandError(UnexpectedResultException ex) {
+ var result = ex.getResult();
+ var commandOutput = ((ExecutableAttributesWithCapturedOutput)result.execAttrs()).printableOutput();
+ var printableCommandLine = result.execAttrs().printableCommandLine();
+
+ if (verbose) {
+ stackTracePrinter.accept(ex);
+ }
String msg;
- if (isAlienExceptionType(t)) {
- msg = t.toString();
+ if (ex instanceof UnexpectedExitCodeException) {
+ msg = I18N.format("error.command-failed-unexpected-exit-code", result.getExitCode(), printableCommandLine);
+ } else if (result.exitCode().isPresent()) {
+ msg = I18N.format("error.command-failed-unexpected-output", printableCommandLine);
} else {
+ msg = I18N.format("error.command-failed-timed-out", printableCommandLine);
+ }
+
+ messagePrinter.accept(I18N.format("message.error-header", msg));
+ if (!verbose) {
+ messagePrinter.accept(I18N.format("message.failed-command-output-header"));
+ try (var lines = new BufferedReader(new StringReader(commandOutput)).lines()) {
+ lines.forEach(messagePrinter);
+ }
+ }
+ }
+
+ private void printError(Throwable t, Optional advice) {
+ var isSelfContained = isSelfContained(t);
+
+ if (!isSelfContained || verbose) {
+ stackTracePrinter.accept(t);
+ }
+
+ String msg;
+ if (isSelfContained) {
msg = t.getMessage();
+ } else {
+ msg = t.toString();
}
messagePrinter.accept(I18N.format("message.error-header", msg));
advice.ifPresent(v -> messagePrinter.accept(I18N.format("message.advice-header", v)));
}
- private static boolean isAlienExceptionType(Throwable t) {
- switch (t) {
- case JPackageException _ -> {
- return false;
- }
- case Utils.ParseException _ -> {
- return false;
- }
- case StandardOption.AddLauncherIllegalArgumentException _ -> {
- return false;
- }
- default -> {
- return true;
- }
- }
+ private static boolean isSelfContained(Throwable t) {
+ return t.getClass().getAnnotation(SelfContainedException.class) != null;
}
}
- static final class Runner {
+ record Runner(Consumer errorReporter) {
+
+ Runner {
+ Objects.requireNonNull(errorReporter);
+ }
int run(Supplier extends Collection extends Exception>> r) {
final var exceptions = runIt(r);
if (exceptions.isEmpty()) {
return 0;
} else {
- var errorReporter = new ErrorReporter(Log::verbose, Log::fatalError);
- exceptions.forEach(errorReporter::reportError);
+ exceptions.forEach(errorReporter);
return 1;
}
}
@@ -241,4 +331,19 @@ public final class Main {
private static String getVersion() {
return System.getProperty("java.version");
}
+
+ private static PrintWriter toPrintWriter(PrintStream ps) {
+ return new PrintWriter(ps, true, ps.charset());
+ }
+
+ private enum DefaultBundlingEnvironmentLoader implements Supplier {
+ INSTANCE;
+
+ @Override
+ public CliBundlingEnvironment get() {
+ return ServiceLoader.load(
+ CliBundlingEnvironment.class,
+ CliBundlingEnvironment.class.getClassLoader()).findFirst().orElseThrow();
+ }
+ }
}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionArrayValueConverter.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionArrayValueConverter.java
index 401d5e15d28..9b757c29d36 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionArrayValueConverter.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionArrayValueConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, 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
@@ -29,7 +29,7 @@ package jdk.jpackage.internal.cli;
*
* @param option value element type
*/
-interface OptionArrayValueConverter extends OptionValueConverter {
+interface OptionArrayValueConverter extends OptionValueConverter {
/**
* Splits the given string into tokens and returns the result.
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpec.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpec.java
index c6aa46a6940..60674e6bdfe 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpec.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpec.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, 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
@@ -30,6 +30,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
+import jdk.jpackage.internal.util.Result;
/**
@@ -56,7 +57,7 @@ import java.util.stream.Stream;
*/
record OptionSpec(
List names,
- Optional> converter,
+ Optional> converter,
Set scope,
MergePolicy mergePolicy,
Optional defaultOptionalValue,
@@ -134,7 +135,7 @@ record OptionSpec(
});
}
- OptionSpec copyWithConverter(OptionValueConverter converter) {
+ OptionSpec copyWithConverter(OptionValueConverter converter) {
if (!defaultOptionalValue.isEmpty()) {
throw new UnsupportedOperationException("Can not convert an option spec with optional value");
}
@@ -169,12 +170,16 @@ record OptionSpec(
return valueType(converter).orElseThrow();
}
+ Result convert(OptionName optionName, StringToken optionValue) {
+ return OptionValueConverter.convertString(converter().orElseThrow(), optionName, optionValue);
+ }
+
@SuppressWarnings("unchecked")
Optional> arrayValueConverter() {
return converter.filter(OptionArrayValueConverter.class::isInstance).map(v -> (OptionArrayValueConverter)v);
}
- private static Optional> valueType(Optional> valueConverter) {
+ private static Optional> valueType(Optional> valueConverter) {
return valueConverter.map(OptionValueConverter::valueType);
}
}
diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpecBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpecBuilder.java
index e27d6472369..5eecbc9b464 100644
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpecBuilder.java
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpecBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, 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
@@ -24,6 +24,8 @@
*/
package jdk.jpackage.internal.cli;
+import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction;
+
import java.io.File;
import java.lang.reflect.Array;
import java.util.Arrays;
@@ -61,22 +63,49 @@ final class OptionSpecBuilder {
this.valueType = Objects.requireNonNull(valueType);
}
- OptionSpecBuilder(OptionSpecBuilder other) {
+ private OptionSpecBuilder(OptionSpecBuilder other) {
valueType = other.valueType;
+ initFrom(other);
+ defaultValue = other.defaultValue;
+ defaultOptionalValue = other.defaultOptionalValue;
+ converterBuilder = other.converterBuilder.copy();
+ validatorBuilder = other.validatorBuilder.copy();
+ validator = other.validator;
+
+ if (other.arrayDefaultValue != null) {
+ arrayDefaultValue = Arrays.copyOf(other.arrayDefaultValue, other.arrayDefaultValue.length);
+ } else {
+ arrayDefaultValue = null;
+ }
+ }
+
+ private OptionSpecBuilder(OptionSpecBuilder other, ValueConverter converter) {
+ Function converterFunction = toFunction(converter::convert);
+
+ this.valueType = converter.valueType();
+ initFrom(other);
+ converter(other, converter);
+
+ other.defaultValue().map(converterFunction).ifPresent(this::defaultValue);
+ other.defaultOptionalValue().map(converterFunction).ifPresent(this::defaultOptionalValue);
+
+ if (other.arrayDefaultValue != null) {
+ arrayDefaultValue = Stream.of(other.arrayDefaultValue).map(converterFunction).toArray(length -> {
+ @SuppressWarnings("unchecked")
+ var arr = (T[])Array.newInstance(valueType, length);
+ return arr;
+ });
+ }
+ }
+
+ private void initFrom(OptionSpecBuilder> other) {
name = other.name;
+ nameAliases.clear();
nameAliases.addAll(other.nameAliases);
description = other.description;
mergePolicy = other.mergePolicy;
scope = Set.copyOf(other.scope);
- defaultValue = other.defaultValue;
- defaultOptionalValue = other.defaultOptionalValue;
valuePattern = other.valuePattern;
- converterBuilder = other.converterBuilder.copy();
- validatorBuilder = other.validatorBuilder.copy();
-
- if (other.arrayDefaultValue != null) {
- arrayDefaultValue = Arrays.copyOf(other.arrayDefaultValue, other.arrayDefaultValue.length);
- }
arrayValuePatternSeparator = other.arrayValuePatternSeparator;
arrayTokenizer = other.arrayTokenizer;
}
@@ -85,6 +114,14 @@ final class OptionSpecBuilder {
return new OptionSpecBuilder<>(this);
}
+ OptionSpecBuilder map(ValueConverter converter) {
+ return new OptionSpecBuilder<>(this, converter);
+ }
+
+ OptionSpecBuilder map(Function, OptionSpecBuilder> mapper) {
+ return mapper.apply(this);
+ }
+
Class extends T> valueType() {
return valueType;
}
@@ -135,10 +172,20 @@ final class OptionSpecBuilder {
scope,
OptionSpecBuilder.this.mergePolicy().orElse(MergePolicy.CONCATENATE),
defaultArrayOptionalValue(),
- Optional.of(arryValuePattern()),
+ Optional.of(arrayValuePattern()),
OptionSpecBuilder.this.description().orElse(""));
}
+ Optional extends Validator> createValidator() {
+ return Optional.ofNullable(validator).or(() -> {
+ if (validatorBuilder.hasValidatingMethod()) {
+ return Optional.of(validatorBuilder.create());
+ } else {
+ return Optional.empty();
+ }
+ });
+ }
+
OptionSpecBuilder tokenizer(String splitRegexp) {
Objects.requireNonNull(splitRegexp);
return tokenizer(str -> {
@@ -162,11 +209,13 @@ final class OptionSpecBuilder {
OptionSpecBuilder validatorExceptionFormatString(String v) {
validatorBuilder.formatString(v);
+ validator = null;
return this;
}
- OptionSpecBuilder validatorExceptionFormatString(UnaryOperator mutator) {
- validatorBuilder.formatString(mutator.apply(validatorBuilder.formatString().orElse(null)));
+ OptionSpecBuilder validatorExceptionFormatString(UnaryOperator mapper) {
+ validatorBuilder.formatString(mapper.apply(validatorBuilder.formatString().orElse(null)));
+ validator = null;
return this;
}
@@ -175,18 +224,19 @@ final class OptionSpecBuilder {
return this;
}
- OptionSpecBuilder converterExceptionFormatString(UnaryOperator mutator) {
- converterBuilder.formatString(mutator.apply(converterBuilder.formatString().orElse(null)));
+ OptionSpecBuilder converterExceptionFormatString(UnaryOperator mapper) {
+ converterBuilder.formatString(mapper.apply(converterBuilder.formatString().orElse(null)));
return this;
}
OptionSpecBuilder validatorExceptionFactory(OptionValueExceptionFactory extends RuntimeException> v) {
validatorBuilder.exceptionFactory(v);
+ validator = null;
return this;
}
- OptionSpecBuilder validatorExceptionFactory(UnaryOperator> mutator) {
- return validatorExceptionFactory(mutator.apply(validatorBuilder.exceptionFactory().orElse(null)));
+ OptionSpecBuilder validatorExceptionFactory(UnaryOperator> mapper) {
+ return validatorExceptionFactory(mapper.apply(validatorBuilder.exceptionFactory().orElse(null)));
}
OptionSpecBuilder converterExceptionFactory(OptionValueExceptionFactory extends RuntimeException> v) {
@@ -194,49 +244,68 @@ final class OptionSpecBuilder {
return this;
}
- OptionSpecBuilder converterExceptionFactory(UnaryOperator> mutator) {
- return converterExceptionFactory(mutator.apply(converterBuilder.exceptionFactory().orElse(null)));
+ OptionSpecBuilder converterExceptionFactory(UnaryOperator> mapper) {
+ return converterExceptionFactory(mapper.apply(converterBuilder.exceptionFactory().orElse(null)));
}
OptionSpecBuilder exceptionFormatString(String v) {
return validatorExceptionFormatString(v).converterExceptionFormatString(v);
}
- OptionSpecBuilder exceptionFormatString(UnaryOperator mutator) {
- return validatorExceptionFormatString(mutator).converterExceptionFormatString(mutator);
+ OptionSpecBuilder exceptionFormatString(UnaryOperator mapper) {
+ return validatorExceptionFormatString(mapper).converterExceptionFormatString(mapper);
}
OptionSpecBuilder exceptionFactory(OptionValueExceptionFactory extends RuntimeException> v) {
return validatorExceptionFactory(v).converterExceptionFactory(v);
}
- OptionSpecBuilder exceptionFactory(UnaryOperator> mutator) {
- return validatorExceptionFactory(mutator).converterExceptionFactory(mutator);
+ OptionSpecBuilder exceptionFactory(UnaryOperator> mapper) {
+ return validatorExceptionFactory(mapper).converterExceptionFactory(mapper);
}
- OptionSpecBuilder converter(ValueConverter v) {
+ OptionSpecBuilder converter(ValueConverter v) {
converterBuilder.converter(v);
return this;
}
- OptionSpecBuilder converter(Function v) {
+ OptionSpecBuilder converter(OptionSpecBuilder other, ValueConverter v) {
+ converterBuilder = other.finalizeConverterBuilder().map(v);
+ return this;
+ }
+
+ OptionSpecBuilder interimConverter(OptionSpecBuilder other) {
+ converterBuilder = converterBuilder.map(other.finalizeConverterBuilder());
+ return this;
+ }
+
+ OptionSpecBuilder converter(ValueConverterFunction v) {
return converter(ValueConverter.create(v, valueType));
}
OptionSpecBuilder validator(Predicate v) {
validatorBuilder.predicate(v::test);
+ validator = null;
return this;
}
@SuppressWarnings("overloads")
OptionSpecBuilder validator(Consumer v) {
validatorBuilder.consumer(v::accept);
+ validator = null;
return this;
}
@SuppressWarnings("overloads")
- OptionSpecBuilder