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 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*(?

    - * Source directory is a directory with the applications's classes and other + * Source paths are supposed to contain the applications's classes and other * resources. * - * @return the source directory of this application + * @return the source paths */ - Optional srcDir(); + Collection appDirSources(); /** - * Gets the input content directories of this application. - *

    - * Contents of the content directories will be copied as-is into the dedicated - * location of the application image. + * Gets the source paths that should be copied into + * {@link ApplicationLayout#contentDirectory()} directory of the image of this + * application. * - * @see ApplicationLayout#contentDirectory - * - * @return the input content directories of this application + * @return the source paths */ - List contentDirs(); + Collection contentDirSources(); /** * Gets the unresolved app image layout of this application. @@ -244,8 +244,17 @@ public interface Application extends BundleSpec { /** * Default implementation of {@link Application} interface. */ - record Stub(String name, String description, String version, String vendor, String copyright, Optional srcDir, - List contentDirs, AppImageLayout imageLayout, Optional runtimeBuilder, - List launchers, Map extraAppImageFileData) implements Application { + record Stub( + String name, + String description, + String version, + String vendor, + String copyright, + Collection appDirSources, + Collection contentDirSources, + AppImageLayout imageLayout, + Optional runtimeBuilder, + List launchers, + Map extraAppImageFileData) implements Application { } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/BundleSpec.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/BundleSpec.java index d91662075e3..e6426a9c1b5 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/BundleSpec.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/BundleSpec.java @@ -27,5 +27,5 @@ package jdk.jpackage.internal.model; /** * A generic bundle specification. */ -public interface BundleSpec { +public sealed interface BundleSpec permits Application, Package { } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/AppImagePackageType.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/BundleType.java similarity index 77% rename from src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/AppImagePackageType.java rename to src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/BundleType.java index 4e28bb05aef..009725f3e92 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/AppImagePackageType.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/BundleType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, 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,20 +22,18 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ + package jdk.jpackage.internal.model; -/** - * App image packaging type. - * - * @see StandardPackageType - */ -public final class AppImagePackageType implements PackageType { - private AppImagePackageType() { - } +/** + * Generic bundle type. E.g.: application image, rpm, msi are all bundle types. + */ +public sealed interface BundleType permits PackageType, AppImageBundleType { /** - * Singleton + * Returns a user-facing label of this bundle type. + * @return a user-facing label of this bundle type. */ - public static final AppImagePackageType APP_IMAGE = new AppImagePackageType(); + String label(); } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/BundlingOperationDescriptor.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/BundlingOperationDescriptor.java index 70e51c849eb..7a736d95514 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/BundlingOperationDescriptor.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/BundlingOperationDescriptor.java @@ -41,6 +41,10 @@ public record BundlingOperationDescriptor(OperatingSystem os, String bundleType, Objects.requireNonNull(verb); } + public BundlingOperationDescriptor(OperatingSystem os, String bundleType) { + this(os, bundleType, VERB_CREATE_BUNDLE); + } + @Override public String toString() { return os.name() + ":" + verb + ":" + bundleType; @@ -50,4 +54,6 @@ public record BundlingOperationDescriptor(OperatingSystem os, String bundleType, final var components = str.split(":"); return new BundlingOperationDescriptor(OperatingSystem.valueOf(components[0]), components[1], components[2]); } + + public static final String VERB_CREATE_BUNDLE = "create"; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/ExecutableAttributesWithCapturedOutput.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/ExecutableAttributesWithCapturedOutput.java new file mode 100644 index 00000000000..faed9d24d01 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/ExecutableAttributesWithCapturedOutput.java @@ -0,0 +1,53 @@ +/* + * 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.model; + +import java.util.List; +import java.util.Objects; +import jdk.jpackage.internal.util.CommandOutputControl.ExecutableAttributes; +import jdk.jpackage.internal.util.CommandOutputControl.Result; + +/** + * {@link ExecutableAttributes} augmented with printable command output. + */ +public record ExecutableAttributesWithCapturedOutput(ExecutableAttributes execAttrs, String printableOutput) + implements ExecutableAttributes { + + public ExecutableAttributesWithCapturedOutput { + Objects.requireNonNull(execAttrs); + Objects.requireNonNull(printableOutput); + } + + @Override + public List commandLine() { + return execAttrs.commandLine(); + } + + public static Result augmentResultWithOutput(Result result, String output) { + var execAttrs = new ExecutableAttributesWithCapturedOutput(result.execAttrs(), output); + return result.copyWithExecutableAttributes(execAttrs); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/JPackageException.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/JPackageException.java index 9727f21d2d7..3d24d293f18 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/JPackageException.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/JPackageException.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 @@ -30,6 +30,7 @@ import java.util.Objects; /** * Generic jpackage exception with non-null message. */ +@SelfContainedException public class JPackageException extends RuntimeException { public JPackageException(String msg) { diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Package.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Package.java index 33c707b776f..ec5d6341d4d 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Package.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Package.java @@ -66,7 +66,7 @@ import java.util.Optional; * * */ -public interface Package extends BundleSpec { +public non-sealed interface Package extends BundleSpec { /** * Gets the application of this package. diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/PackageType.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/PackageType.java index d0a4fd010e6..e7273d27ba5 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/PackageType.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/PackageType.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,8 +27,8 @@ package jdk.jpackage.internal.model; /** - * Generic package type. E.g.: application image, rpm, msi are all package types. + * Native package type. E.g.: dmg, rpm, msi are all package types. * * @see jdk.jpackage.internal.model.Package */ -public interface PackageType {} +public non-sealed interface PackageType extends BundleType {} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/SelfContainedException.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/SelfContainedException.java new file mode 100644 index 00000000000..c7d1b3016ac --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/SelfContainedException.java @@ -0,0 +1,42 @@ +/* + * 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.model; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for exceptions with self-contained error messages that don't + * require an additional context, like exception class name or a stack trace. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface SelfContainedException { +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/StandardPackageType.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/StandardPackageType.java index ccdeceb4a04..6fadc748ecc 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/StandardPackageType.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/StandardPackageType.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,19 +24,22 @@ */ package jdk.jpackage.internal.model; +import java.util.Objects; + /** * Standard native package types. */ public enum StandardPackageType implements PackageType { - WIN_MSI(".msi"), - WIN_EXE(".exe"), - LINUX_DEB(".deb"), - LINUX_RPM(".rpm"), - MAC_PKG(".pkg"), - MAC_DMG(".dmg"); + WIN_MSI("bundle-type.win-msi", ".msi"), + WIN_EXE("bundle-type.win-exe", ".exe"), + LINUX_DEB("bundle-type.linux-deb", ".deb"), + LINUX_RPM("bundle-type.linux-rpm", ".rpm"), + MAC_PKG("bundle-type.mac-pkg", ".pkg"), + MAC_DMG("bundle-type.mac-dmg", ".dmg"); - StandardPackageType(String suffix) { - this.suffix = suffix; + StandardPackageType(String key, String suffix) { + this.key = Objects.requireNonNull(key); + this.suffix = Objects.requireNonNull(suffix); } /** @@ -48,5 +51,11 @@ public enum StandardPackageType implements PackageType { return suffix; } + @Override + public String label() { + return I18N.getString(key); + } + + private final String key; private final String suffix; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources.properties b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources.properties index c61e60fe4ec..6e5de3d9729 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources.properties +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2017, 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,14 +28,14 @@ param.copyright.default=Copyright (C) {0,date,YYYY} param.vendor.default=Unknown bundle-type.win-app=Windows Application Image -bundle-type.win-exe=EXE Installer Package -bundle-type.win-msi=MSI Installer Package +bundle-type.win-exe=Windows EXE Installer +bundle-type.win-msi=Windows MSI Installer bundle-type.mac-app=Mac Application Image bundle-type.mac-dmg=Mac DMG Package bundle-type.mac-pkg=Mac PKG Package bundle-type.linux-app=Linux Application Image -bundle-type.linux-deb=DEB Bundle -bundle-type.linux-rpm=RPM Bundle +bundle-type.linux-deb=Linux DEB Package +bundle-type.linux-rpm=Linux RPM Package resource.post-app-image-script=script to run after application image is populated @@ -43,13 +43,23 @@ message.using-default-resource=Using default package resource {0} {1} (add {2} t message.no-default-resource=No default package resource {0} (add {1} to the resource-dir to customize). message.using-custom-resource-from-file=Using custom package resource {0} (loaded from file {1}). message.using-custom-resource=Using custom package resource {0} (loaded from {1}). -message.creating-app-bundle=Creating app package: {0} in {1} + +message.create-package=Building output package file... +message.create-app-image=Building output application image directory... +message.package-created=Succeeded in building output package file +message.app-image-created=Succeeded in building output application image directory + message.debug-working-directory=Kept working directory for debug: {0} -message.bundle-created=Succeeded in building {0} package + message.module-version=Using version "{0}" from module "{1}" as application version -message.error-header={0} +message.error-header=Error: {0} message.advice-header=Advice to fix: {0} +message.failed-command-output-header=Command output: + +error.command-failed-unexpected-output=Unexpected output from executing the command {0} +error.command-failed-unexpected-exit-code=Unexpected exit code {0} from executing the command {1} +error.command-failed-timed-out=Timed-out command {0} error.version-string-empty=Version may not be empty string error.version-string-zero-length-component=Version [{0}] contains a zero length component @@ -75,13 +85,16 @@ error.parameter-not-directory=The value "{0}" provided for parameter {1} is not error.parameter-not-empty-directory=The value "{0}" provided for parameter {1} is not an empty directory or non existent path error.parameter-not-url=The value "{0}" provided for parameter {1} is not a valid URL error.parameter-not-launcher-shortcut-dir=The value "{0}" provided for parameter {1} is not a valid shortcut startup directory +error.parameter-not-mac-bundle=The value "{0}" provided for parameter {1} is not a valid macOS bundle error.path-parameter-ioexception=I/O error accessing path value "{0}" of parameter {1} error.parameter-add-launcher-malformed=The value "{0}" provided for parameter {1} does not match the pattern = error.parameter-add-launcher-not-file=The value of path to a property file "{0}" provided for additional launcher "{1}" is not a valid file path error.properties-parameter-not-path=The value "{0}" provided for property "{1}" in "{2}" file is not a valid path error.properties-parameter-not-file=The value "{0}" provided for property "{1}" in "{2}" file is not a file +error.properties-parameter-not-directory=The value "{0}" provided for property "{1}" in "{2}" file is not a directory error.properties-parameter-not-launcher-shortcut-dir=The value "{0}" provided for property "{1}" in "{2}" file is not a valid shortcut startup directory +error.no-extensions-for-file-association=No extensions were specified for File Association number {0} error.no-content-types-for-file-association=No MIME types were specified for File Association number {0} error.no-content-types-for-file-association.advice=Specify MIME type for File Association number {0} error.too-many-content-types-for-file-association=More than one MIME types was specified for File Association number {0} @@ -96,7 +109,8 @@ error.tool-not-found.advice=Please install "{0}" error.tool-old-version=Can not find "{0}" {1} or newer error.tool-old-version.advice=Please install "{0}" {1} or newer -error.jlink.failed=jlink failed with: {0} +error.output-bundle-cannot-be-overwritten=Output package file "{0}" exists and can not be removed. + error.blocked.option=jlink option [{0}] is not permitted in --jlink-options error.no.name=Name not specified with --name and cannot infer one from app-image error.no.name.advice=Specify name with --name diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/ResourceLocator.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/ResourceLocator.java index 93d317b0128..cb03a1fee8a 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/ResourceLocator.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/ResourceLocator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2019, 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/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandLineFormat.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandLineFormat.java new file mode 100644 index 00000000000..4d64030876f --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandLineFormat.java @@ -0,0 +1,52 @@ +/* + * 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.util; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Formats command line arguments. + */ +public final class CommandLineFormat { + + public String format(List cmdline) { + return cmdline.stream().map(enquoter::applyTo).collect(Collectors.joining(" ")); + } + + public static CommandLineFormat platform() { + var format = new CommandLineFormat(); + format.enquoter = Enquoter.identity().setEnquotePredicate(Enquoter.QUOTE_IF_WHITESPACES).setQuoteChar('\''); + return format; + } + + private CommandLineFormat() { + } + + private Enquoter enquoter; + + public static final Function, String> DEFAULT = platform()::format; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandOutputControl.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandOutputControl.java new file mode 100644 index 00000000000..00fbdd71919 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandOutputControl.java @@ -0,0 +1,1949 @@ +/* + * 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.util; + +import static java.util.stream.Collectors.joining; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.StringReader; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntPredicate; +import java.util.function.Predicate; +import java.util.spi.ToolProvider; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import jdk.jpackage.internal.util.function.ExceptionBox; +import jdk.jpackage.internal.util.function.ThrowingConsumer; +import jdk.jpackage.internal.util.function.ThrowingRunnable; + +/** + * Runs commands and processes their stdout and stderr streams. + *

    + * A command is either a subprocess represented by {@link ProcessBuilder} or a + * tool provided by {@link ToolProvider}. + *

    + * A command is executed synchronously, and the result of its execution is + * stored in a {@link Result} instance which captures the exit code and any + * saved output streams. + *

    + * Depending on the configuration, it can save the entire output stream, only + * the first line, or not save the output at all. Stdout and stderr streams can + * be configured independently. + *

    + * Output streams can be treated as either byte streams or character streams. + * + *

    + * The table below shows how different parameter combinations affect the content + * written to streams returned by {@link #dumpStdout()} and + * {@link #dumpStderr()} for subsequently executed tools, regardless of whether + * their output streams are saved, or for subprocesses when the output streams + * are saved: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    discardStdout(false) and discardStderr(false)discardStdout(false) and discardStderr(true)discardStdout(true) and discardStderr(false)
    redirectStderr(true) and dumpOutput(true) + *

    + * dumpStdout(): STDOUT and STDERR interleaved + *

    + * dumpStderr(): unchanged

    + *

    + * dumpStdout(): STDOUT + *

    + * dumpStderr(): unchanged

    + *

    + * dumpStdout(): STDERR; + *

    + * dumpStderr(): unchanged

    redirectStderr(false) and dumpOutput(true) + *

    + * dumpStdout(): STDOUT + *

    + * dumpStderr(): STDERR

    + *

    + * dumpStdout(): STDOUT + *

    + * dumpStderr(): unchanged

    + *

    + * dumpStdout(): unchanged + *

    + * dumpStderr(): STDERR

    + * + *

    + * The table below shows how different parameter combinations affect the content + * written to the native file descriptors associated with {@link System#out} and + * {@link System#err} for subsequently executed subprocesses when the output + * streams are not saved: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    discardStdout(false) and discardStderr(false)discardStdout(false) and discardStderr(true)discardStdout(true) and discardStderr(false)
    redirectStderr(true) and dumpOutput(true) + *

    + * System.out: STDOUT and STDERR interleaved + *

    + * System.err: unchanged

    + *

    + * System.out: STDOUT + *

    + * System.err: unchanged

    + *

    + * System.out: STDERR; + *

    + * The command's STDERR will be written to the stream referenced by + * {@link #dumpStdout()} rather than to the underlying file descriptor + * associated with the Java process's STDOUT + *

    + * System.err: unchanged

    redirectStderr(false) and dumpOutput(true) + *

    + * System.out: STDOUT + *

    + * System.err: STDERR

    + *

    + * System.out: STDOUT + *

    + * System.err: unchanged

    + *

    + * System.out: unchanged + *

    + * System.err: STDERR

    + * + *

    + * The table below shows how different parameter combinations affect the + * properties of {@link Result} objects returned by + * {@link #execute(ProcessBuilder, long)} or + * {@link #execute(ToolProvider, long, String...)} when processing character + * streams: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    saveOutput(true)saveFirstLineOfOutput()
    redirectStderr(true) and discardStdout(false) and + * discardStderr(false) + *

    + * content(): STDOUT and STDERR interleaved + *

    + * findStdout(): {@code Optional.empty()} + *

    + * findStderr(): {@code Optional.empty()}

    + *

    + * content(): a single-item list containing the first line of interleaved STDOUT + * and STDERR if the command produced any output; otherwise, an empty list + *

    + * findStdout(): {@code Optional.empty()} + *

    + * findStderr(): {@code Optional.empty()}

    redirectStderr(false) and discardStdout(false) and + * discardStderr(false) + *

    + * content(): STDOUT followed by STDERR + *

    + * stdout(): STDOUT + *

    + * stderr(): STDERR

    + *

    + * content(): a list containing at most two items: the first line of STDOUT (if + * the command produced any), followed by the first line of STDERR (if the + * command produced any) + *

    + * stdout(): The first line of STDOUT (if the command produced any); otherwise + * an empty list + *

    + * findStderr(): The first line of STDERR (if the command produced any); + * otherwise an empty list + *

    redirectStderr(true) and discardStdout(false) and + * discardStderr(true) + *

    + * content(): STDOUT + *

    + * stdout(): The same as content() + *

    + * findStderr(): {@code Optional.empty()}

    + *

    + * content(): The first line of STDOUT (if the command produced any); otherwise + * an empty list + *

    + * stdout(): The same as content() + *

    + * findStderr(): {@code Optional.empty()}

    redirectStderr(false) and discardStdout(false) and + * discardStderr(true) + *

    + * content(): STDOUT + *

    + * stdout(): The same as content() + *

    + * stderr(): an empty list

    + *

    + * content(): The first line of STDOUT (if the command produced any); otherwise + * an empty list + *

    + * stdout(): The same as content() + *

    + * stderr(): an empty list

    redirectStderr(true) and discardStdout(true) and + * discardStderr(false) + *

    + * content(): STDERR + *

    + * stdout(): The same as content() + *

    + * findStderr(): {@code Optional.empty()}

    + *

    + * content(): The first line of STDERR (if the command produced any); otherwise + * an empty list + *

    + * stdout(): The same as content() + *

    + * findStderr(): {@code Optional.empty()}

    redirectStderr(false) and discardStdout(true) and + * discardStderr(false) + *

    + * content(): STDERR + *

    + * findStdout(): an empty list + *

    + * stderr(): The same as content()

    + *

    + * content(): The first line of STDERR (if the command produced any); otherwise + * an empty list + *

    + * findStdout(): an empty list + *

    + * stderr(): The same as content()

    + *

    + * The table below shows how different parameter combinations affect the + * properties of {@link Result} objects returned by + * {@link #execute(ProcessBuilder, long)} or + * {@link #execute(ToolProvider, long, String...)} when processing byte streams: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    saveOutput(true) or saveFirstLineOfOutput()
    redirectStderr(true) and discardStdout(false) and + * discardStderr(false) + *

    + * byteContent(): STDOUT and STDERR interleaved + *

    + * findByteStdout(): {@code Optional.empty()} + *

    + * findByteStderr(): {@code Optional.empty()}

    redirectStderr(false) and discardStdout(false) and + * discardStderr(false) + *

    + * byteContent(): STDOUT followed by STDERR + *

    + * byteStdout(): STDOUT + *

    + * byteStderr(): STDERR

    redirectStderr(true) and discardStdout(false) and + * discardStderr(true) + *

    + * byteContent(): STDOUT + *

    + * byteStdout(): The same as byteContent() + *

    + * findByteStderr(): {@code Optional.empty()}

    redirectStderr(false) and discardStdout(false) and + * discardStderr(true) + *

    + * byteContent(): STDOUT + *

    + * byteStdout(): The same as byteContent() + *

    + * byteStderr(): an empty array

    redirectStderr(true) and discardStdout(true) and + * discardStderr(false) + *

    + * byteContent(): STDERR + *

    + * byteStdout(): The same as byteContent() + *

    + * findByteStderr(): {@code Optional.empty()}

    redirectStderr(false) and discardStdout(true) and + * discardStderr(false) + *

    + * byteContent(): STDERR + *

    + * findByteStdout(): an empty array + *

    + * byteStderr(): The same as byteContent()

    + */ +public final class CommandOutputControl { + + public CommandOutputControl() { + outputStreamsControl = new OutputStreamsControl(); + } + + private CommandOutputControl(CommandOutputControl other) { + flags = other.flags; + outputStreamsControl = other.outputStreamsControl.copy(); + dumpStdout = other.dumpStdout; + dumpStderr = other.dumpStderr; + charset = other.charset; + processListener = other.processListener; + } + + /** + * Specifies whether the full output produced by commands subsequently executed + * by this object will be saved. + *

    + * If {@code v} is {@code true}, both stdout and stderr streams will be saved; + * otherwise, they will not be saved. + *

    + * This setting is mutually exclusive with {@link #saveFirstLineOfOutput()}. + * + * @param v {@code true} to save the full stdout and stderr streams; + * {@code false} otherwise + * @return this + */ + public CommandOutputControl saveOutput(boolean v) { + return setOutputControl(v, OutputControlOption.SAVE_ALL); + } + + /** + * Returns whether this object will save the complete output of commands + * subsequently executed. + * + * @return {@code true} if this object will save the full output of commands it + * executes subsequently; {@code false} otherwise + */ + public boolean isSaveOutput() { + return outputStreamsControl.stdout().saveAll(); + } + + /** + * Specifies whether the first line of the output, combined from the stdout and + * stderr streams of commands subsequently executed by this object, will be + * saved. + *

    + * This setting is mutually exclusive with {@link #saveOutput(boolean)}. + * + * @return this + */ + public CommandOutputControl saveFirstLineOfOutput() { + return setOutputControl(true, OutputControlOption.SAVE_FIRST_LINE); + } + + /** + * Returns whether this object will save the first line of the output of + * commands subsequently executed. + * + * @return {@code true} if this object will save the first line of the output of + * commands it executes subsequently; {@code false} otherwise + */ + public boolean isSaveFirstLineOfOutput() { + return outputStreamsControl.stdout().saveFirstLine(); + } + + /** + * Specifies whether this object will dump the output streams from + * subsequently executed commands into the streams returned by + * {@link #dumpStdout()} and {@link #dumpStdout()} methods respectively. + *

    + * If this object is configured to redirect stderr of subsequently executed + * commands into their stdout ({@code redirectStderr(true)}), their output + * streams will be dumped into the stream returned by {@code dumpStdout()} + * method. Otherwise, their stdout and stderr streams will be dumped into the + * stream returned by {@code dumpStdout()} and {@code dumpStderr()} methods + * respectively. + * + * @param v if output streams from subsequently executed commands will be + * dumped into streams returned by {@code dumpStdout()} and + * {@code dumpStderr()} methods respectively + * + * @return this + * + * @see #redirectStderr(boolean) + * @see #dumpStdout() + * @see #dumpStderr() + */ + public CommandOutputControl dumpOutput(boolean v) { + setFlag(Flag.DUMP, v); + return setOutputControl(v, OutputControlOption.DUMP); + } + + /** + * Returns the value passed in the last call of {@link #dumpOutput(boolean)} + * method on this object, or {@code false} if the method has not been called. + * + * @return the value passed in the last call of {@link #dumpOutput(boolean)} + */ + public boolean isDumpOutput() { + return Flag.DUMP.isSet(flags); + } + + /** + * Specifies whether this object will treat output streams of subsequently + * executed commands as byte streams rather than character streams. + * + * @param v {@code true} if this object will treat the output streams of + * subsequently executed commands as byte streams, and {@code false} + * otherwise + * + * @return this + */ + public CommandOutputControl binaryOutput(boolean v) { + return setFlag(Flag.BINARY_OUTPUT, v); + } + + /** + * Returns the value passed in the last call of {@link #binaryOutput(boolean)} + * method on this object, or {@code false} if the method has not been called. + * + * @return the value passed in the last call of {@link #binaryOutput(boolean)} + */ + public boolean isBinaryOutput() { + return Flag.BINARY_OUTPUT.isSet(flags); + } + + /** + * Sets character encoding that will be applied to the stdout and the stderr + * streams of commands (subprocesses and {@code ToolProvider}-s) subsequently + * executed by this object. The default encoding is {@code UTF-8}. + *

    + * The value will be ignored if this object is configured for byte output + * streams. + * + * @param v character encoding for output streams of subsequently executed + * commands + * + * @see #binaryOutput(boolean) + * + * @return this + */ + public CommandOutputControl charset(Charset v) { + charset = v; + return this; + } + + /** + * Returns the value passed in the last call of + * {@link #charset(Charset)} method on this object, or + * {@link StandardCharsets#UTF_8} if the method has not been called. + * + * @return the character encoding that will be applied to the stdout and stderr + * streams of commands subsequently executed by this object + */ + public Charset charset() { + return Optional.ofNullable(charset).orElse(StandardCharsets.UTF_8); + } + + /** + * Specifies whether the stderr stream will be redirected into the stdout stream + * for commands subsequently executed by this object. + * + * @see ProcessBuilder#redirectErrorStream(boolean) + * + * @param v {@code true} if the stderr stream of commands subsequently executed + * by this object will be redirected into the stdout stream; + * {@code false} otherwise + * + * @return this + */ + public CommandOutputControl redirectStderr(boolean v) { + return setFlag(Flag.REDIRECT_STDERR, v); + } + + /** + * Returns the value passed in the last call of {@link #redirectStderr(boolean)} + * method on this object, or {@code false} if the method has not been called. + * + * @return the value passed in the last call of {@link #redirectStderr(boolean)} + */ + public boolean isRedirectStderr() { + return Flag.REDIRECT_STDERR.isSet(flags); + } + + /** + * Specifies whether stderr and stdout streams for subprocesses subsequently + * executed by this object will be stored in files. + *

    + * By default, if an output stream of a subprocess is configured for saving, + * this object will retrieve the content using {@link Process#getInputStream()} + * function for stdout and {@link Process#getErrorStream()} function for stderr. + * However, these functions don't always work correctly due to a + * JDK-8236825 bug + * still reproducible on macOS JDK26. The alternative way to get the content of + * output streams of a subprocess is to redirect them into files and read these + * files when the subprocess terminates. + *

    + * It will use {@code Files.createTempFile("jpackageOutputTempFile", ".tmp")} to + * create a file for each subprocess's output stream configured for saving. All + * created files will be automatically deleted at the exit of + * {@link #execute(ProcessBuilder, long)} method. + *

    + * Doesn't apply to executing {@code ToolProvider}-s. + *

    + * Storing output streams in files takes longer than managing them in memory and + * should be avoided if possible. + * + * @param v {@code true} if this object will use files to store saved output + * streams of subsequently executed subprocesses; {@code false} + * otherwise + * @return this + */ + public CommandOutputControl storeOutputInFiles(boolean v) { + return setFlag(Flag.STORE_OUTPUT_IN_FILES, v); + } + + /** + * Returns the value passed in the last call of {@link #storeOutputInFiles(boolean)} + * method on this object, or {@code false} if the method has not been called. + * + * @return the value passed in the last call of {@link #storeOutputInFiles(boolean)} + */ + public boolean isStoreOutputInFiles() { + return Flag.STORE_OUTPUT_IN_FILES.isSet(flags); + } + + /** + * Specifies whether stdout streams from commands subsequently executed by this + * object will be discarded. + * + * @param v {@code true} if this object will discard stdout streams from + * commands subsequently executed by this object; {@code false} + * otherwise + * @return this + */ + public CommandOutputControl discardStdout(boolean v) { + setFlag(Flag.DISCARD_STDOUT, v); + outputStreamsControl.stdout().discard(v); + return this; + } + + /** + * Returns the value passed in the last call of {@link #discardStdout(boolean)} + * method on this object, or {@code false} if the method has not been called. + * + * @return the value passed in the last call of {@link #discardStdout(boolean)} + */ + public boolean isDiscardStdout() { + return Flag.DISCARD_STDOUT.isSet(flags); + } + + /** + * Specifies whether stderr streams from commands subsequently executed by this + * object will be discarded. + * + * @param v {@code true} if this object will discard stderr streams from + * commands subsequently executed by this object; {@code false} + * otherwise + * @return this + */ + public CommandOutputControl discardStderr(boolean v) { + setFlag(Flag.DISCARD_STDERR, v); + outputStreamsControl.stderr().discard(v); + return this; + } + + /** + * Returns the value passed in the last call of {@link #discardStderr(boolean)} + * method on this object, or {@code false} if the method has not been called. + * + * @return the value passed in the last call of {@link #discardStderr(boolean)} + */ + public boolean isDiscardStderr() { + return Flag.DISCARD_STDERR.isSet(flags); + } + + /** + * Specifies the stream where stdout streams from commands subsequently executed + * by this object will be dumped. + *

    + * If the {@code null} is specified and this object configuration is equivalent + * to {@code dumpOutput(true).saveOutput(false).discardStdout(false)} the stdout + * streams from commands subsequently executed by this object will be written + * into the file descriptor associated with the {@code Systsem.out} stream. If + * you want them to be written into the {@code Systsem.out} object, pass the + * {@code Systsem.out} reference into this function. + * + * @param v the stream where stdout streams from commands subsequently executed + * by this object will be dumped; {@code null} permitted + * @return this + */ + public CommandOutputControl dumpStdout(PrintStream v) { + dumpStdout = v; + return this; + } + + /** + * Returns the value passed in the last call of {@link #dumpStdout(PrintStream)} + * method on this object, or {@link System#out} if the method has not been + * called. + * + * @return the stream where stdout streams from commands subsequently executed + * by this object will be dumped + */ + public PrintStream dumpStdout() { + return Optional.ofNullable(dumpStdout).orElse(System.out); + } + + /** + * Specifies the stream where stderr streams from commands subsequently executed + * by this object will be dumped. + *

    + * If the {@code null} is specified and this object configuration is equivalent + * to + * {@code dumpOutput(true).saveOutput(false).redirectStderr(false).discardStderr(false)} + * the stderr streams from commands subsequently executed by this object will be + * written into the file descriptor associated with the {@code Systsem.err} + * stream. If you want them to be written into the {@code Systsem.err} object, + * pass the {@code Systsem.err} reference into this function. + * + * @param v the stream where stderr streams from commands subsequently executed + * by this object will be dumped; {@code null} permitted + * @return this + */ + public CommandOutputControl dumpStderr(PrintStream v) { + dumpStderr = v; + return this; + } + + /** + * Returns the value passed in the last call of {@link #dumpStderr(PrintStream)} + * method on this object, or {@link System#err} if the method has not been + * called. + * + * @return the stream where stderr streams from commands subsequently executed + * by this object will be dumped + */ + public PrintStream dumpStderr() { + return Optional.ofNullable(dumpStderr).orElse(System.err); + } + + /** + * Sets the callback to be invoked when this object starts a subprocess from + * subsequent {@link #execute(ProcessBuilder, long)} calls. + * + *

    + * This object maintains a single callback. Calling this method replaces any + * previously set callback. + * + *

    + * The callback is invoked on the thread that calls + * {@link #execute(ProcessBuilder, long)} after the subprocess's output streams + * begin being pumped. + * + * @param v the callback for notifying a subprocess being started or + * {@code null} + * @return this + */ + public CommandOutputControl processListener(Consumer v) { + processListener = v; + return this; + } + + /** + * Returns an {@code Optional} wrapping the value passed in the last call of + * {@link #processListener(Consumer)} method on this object, or an empty + * {@code Optional} if the method has not been called or {@code null} was passed in the last call. + * + * @return an {@code Optional} wrapping the value passed in the last call of + * {@link #processListener(Consumer)} + */ + public Optional> processListener() { + return Optional.ofNullable(processListener); + } + + /** + * Returns a deep copy of this object. Changes to the copy will not affect the + * original. + * + * @return a deep copy of this object + */ + public CommandOutputControl copy() { + return new CommandOutputControl(this); + } + + public interface ExecutableAttributes { + List commandLine(); + + default String printableCommandLine() { + return CommandLineFormat.DEFAULT.apply(commandLine()); + } + } + + public sealed interface Executable { + + ExecutableAttributes attributes(); + + Result execute() throws IOException, InterruptedException; + + Result execute(long timeout, TimeUnit unit) throws IOException, InterruptedException; + } + + public record ProcessAttributes(Optional pid, List commandLine) implements ExecutableAttributes { + public ProcessAttributes { + Objects.requireNonNull(pid); + commandLine.forEach(Objects::requireNonNull); + } + } + + public record ToolProviderAttributes(String name, List args) implements ExecutableAttributes { + public ToolProviderAttributes { + Objects.requireNonNull(name); + args.forEach(Objects::requireNonNull); + } + + @Override + public List commandLine() { + return Stream.concat(Stream.of(name), args.stream()).toList(); + } + } + + public static ExecutableAttributes EMPTY_EXECUTABLE_ATTRIBUTES = new ExecutableAttributes() { + @Override + public List commandLine() { + return List.of(""); + } + }; + + public Executable createExecutable(ToolProvider tp, String... args) { + return new ToolProviderExecutable(tp, List.of(args), this); + } + + public Executable createExecutable(ProcessBuilder pb) { + return new ProcessExecutable(pb, this); + } + + public record Result( + Optional exitCode, + Optional>> output, + Optional> byteOutput, + ExecutableAttributes execAttrs) { + + public Result { + Objects.requireNonNull(exitCode); + Objects.requireNonNull(output); + Objects.requireNonNull(byteOutput); + Objects.requireNonNull(execAttrs); + } + + public static Builder build() { + return new Builder(); + } + + public static final class Builder { + + public Result create() { + return new Result( + exitCode, + Optional.ofNullable(content).map(List::copyOf).map(StringListContent::new).map(c -> { + return new CommandOutput>(Optional.of(c), c.size(), true); + }), + Optional.empty(), + Optional.ofNullable(execAttrs).orElse(EMPTY_EXECUTABLE_ATTRIBUTES)); + } + + public Builder exitCode(int v) { + exitCode = Optional.of(v); + return this; + } + + public Builder noExitCode() { + exitCode = Optional.empty(); + return this; + } + + public Builder execAttrs(ExecutableAttributes v) { + execAttrs = v; + return this; + } + + public Builder content(List v) { + content = v; + return this; + } + + public Builder content(String... v) { + return content(List.of(v)); + } + + private ExecutableAttributes execAttrs; + private List content; + private Optional exitCode = Optional.empty(); + } + + public Result(int exitCode) { + this(Optional.of(exitCode), Optional.empty(), Optional.empty(), EMPTY_EXECUTABLE_ATTRIBUTES); + } + + public int getExitCode() { + return exitCode.orElseThrow(() -> { + return new IllegalStateException("Exit code is unavailable for timed-out command"); + }); + } + + public Result expectExitCode(int main, int... other) throws UnexpectedExitCodeException { + return expectExitCode(v -> { + return IntStream.concat(IntStream.of(main), IntStream.of(other)).boxed().anyMatch(Predicate.isEqual(v)); + }); + } + + public Result expectExitCode(Collection expected) throws UnexpectedExitCodeException { + return expectExitCode(expected::contains); + } + + public Result expectExitCode(IntPredicate expected) throws UnexpectedExitCodeException { + if (!expected.test(getExitCode())) { + throw new UnexpectedExitCodeException(this); + } + return this; + } + + public UnexpectedResultException unexpected() { + return new UnexpectedResultException(this); + } + + public UnexpectedResultException unexpected(String message) { + return new UnexpectedResultException(this, message); + } + + public Optional> findContent() { + return output.flatMap(CommandOutput::combined); + } + + public Optional> findStdout() { + return output.flatMap(CommandOutput::stdout); + } + + public Optional> findStderr() { + return output.flatMap(CommandOutput::stderr); + } + + // For backward compatibility + public List getOutput() { + return content(); + } + + public List content() { + return findContent().orElseThrow(); + } + + public List stdout() { + return findStdout().orElseThrow(); + } + + public List stderr() { + return findStderr().orElseThrow(); + } + + public Optional findByteContent() { + return byteOutput.flatMap(CommandOutput::combined); + } + + public Optional findByteStdout() { + return byteOutput.flatMap(CommandOutput::stdout); + } + + public Optional findByteStderr() { + return byteOutput.flatMap(CommandOutput::stderr); + } + + public byte[] byteContent() { + return findByteContent().orElseThrow(); + } + + public byte[] byteStdout() { + return findByteStdout().orElseThrow(); + } + + public byte[] byteStderr() { + return findByteStderr().orElseThrow(); + } + + public Result toCharacterResult(Charset charset, boolean keepByteContent) throws IOException { + Objects.requireNonNull(charset); + + if (byteOutput.isEmpty()) { + return this; + } + + var theByteOutput = byteOutput.get(); + + try { + Optional>> out; + if (theByteOutput.content().isEmpty()) { + // The content is unavailable. + out = Optional.empty(); + } else if (theByteOutput.stdoutContentSize() == 0) { + // The content is available, but empty. + out = Optional.of(new StringListContent(List.of())); + } else if (theByteOutput.interleaved()) { + // STDOUT and STDERR streams are interleaved. + out = theByteOutput.combined().map(data -> { + return toStringList(data, charset); + }); + } else { + // Non-empty STDOUT not interleaved with STDERR. + out = findByteStdout().map(data -> { + return toStringList(data, charset); + }); + } + + var err = findByteStderr().map(data -> { + return toStringList(data, charset); + }); + + var newOutput = combine(out, err, theByteOutput.interleaved); + + return new Result(exitCode, Optional.of(newOutput), byteOutput.filter(_ -> keepByteContent), execAttrs); + } catch (UncheckedIOException ex) { + throw ex.getCause(); + } + } + + public Result copyWithExecutableAttributes(ExecutableAttributes execAttrs) { + return new Result(exitCode, output, byteOutput, Objects.requireNonNull(execAttrs)); + } + + private static StringListContent toStringList(byte[] data, Charset charset) { + try (var bufReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(data), charset))) { + return new StringListContent(bufReader.lines().toList()); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + } + + public static sealed class UnexpectedResultException extends IOException { + + private UnexpectedResultException(Result value, String message) { + super(Objects.requireNonNull(message)); + this.value = Objects.requireNonNull(value); + } + + private UnexpectedResultException(Result value) { + this(value, String.format("Unexpected result from executing the command %s", + value.execAttrs().printableCommandLine())); + } + + public Result getResult() { + return value; + } + + private final transient Result value; + + private static final long serialVersionUID = 1L; + } + + public static final class UnexpectedExitCodeException extends UnexpectedResultException { + + public UnexpectedExitCodeException(Result value, String message) { + super(requireExitCode(value), message); + } + + public UnexpectedExitCodeException(Result value) { + this(value, String.format("Unexpected exit code %d from executing the command %s", + requireExitCode(value).getExitCode(), value.execAttrs().printableCommandLine())); + } + + private static Result requireExitCode(Result v) { + Objects.requireNonNull(v); + if (v.exitCode().isPresent()) { + return v; + } else { + throw new IllegalArgumentException(); + } + } + + private static final long serialVersionUID = 1L; + } + + public String description() { + var tokens = outputStreamsControl.descriptionTokens(); + if (isBinaryOutput()) { + tokens.add("byte"); + } + if (redirectRetainedStderr()) { + tokens.add("interleave"); + } + return String.join("; ", tokens); + } + + private Result execute(ProcessBuilder pb, long timeoutMillis) + throws IOException, InterruptedException { + + Objects.requireNonNull(pb); + + var theCharset = charset(); + + configureProcessBuilder(pb); + + var csc = new CachingStreamsConfig(); + + var process = pb.start(); + + BiConsumer gobbler = (in, ps) -> { + try { + if (isBinaryOutput()) { + try (in) { + in.transferTo(ps); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } else { + try (var bufReader = new BufferedReader(new InputStreamReader(in, theCharset))) { + bufReader.lines().forEach(ps::println); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + } finally { + suppressIOException(ps::flush); + } + }; + + // Start fetching process output streams. + // Do it before waiting for the process termination to avoid deadlocks. + + final Optional> stdoutGobbler; + if (mustReadOutputStream(pb.redirectOutput())) { + stdoutGobbler = Optional.of(CompletableFuture.runAsync(() -> { + gobbler.accept(process.getInputStream(), csc.out()); + }, gobblerExecutor)); + } else { + stdoutGobbler = Optional.empty(); + } + + final Optional> stderrGobbler; + if (!pb.redirectErrorStream() && mustReadOutputStream(pb.redirectError())) { + stderrGobbler = Optional.of(CompletableFuture.runAsync(() -> { + gobbler.accept(process.getErrorStream(), csc.err()); + }, gobblerExecutor)); + } else { + stderrGobbler = Optional.empty(); + } + + processListener().ifPresent(c -> { + c.accept(process); + }); + + final Optional exitCode; + if (timeoutMillis < 0) { + exitCode = Optional.of(process.waitFor()); + } else if (!process.waitFor(timeoutMillis, TimeUnit.MILLISECONDS)) { + // Destroy the process and cancel the process output stream gobblers. + process.destroy(); + for (var g : List.of(stdoutGobbler, stderrGobbler)) { + g.ifPresent(future -> { + future.cancel(true); + }); + } + exitCode = Optional.empty(); + } else { + exitCode = Optional.of(process.exitValue()); + } + + try { + if (isStoreOutputInFiles()) { + var stdoutStorage = streamFileSink(pb.redirectOutput()); + var stderrStorage = streamFileSink(pb.redirectError()); + + Function toInputStream = path -> { + try { + return Files.newInputStream(path); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }; + + try { + stdoutStorage.map(toInputStream).ifPresent(in -> { + gobbler.accept(in, csc.out()); + }); + + stderrStorage.map(toInputStream).ifPresent(in -> { + gobbler.accept(in, csc.err()); + }); + } finally { + Consumer silentDeleter = path -> { + suppressIOException(Files::delete, path); + }; + + stdoutStorage.ifPresent(silentDeleter); + stderrStorage.ifPresent(silentDeleter); + } + } else { + stdoutGobbler.ifPresent(CommandOutputControl::join); + stderrGobbler.ifPresent(CommandOutputControl::join); + } + } catch (UncheckedIOException ex) { + throw ex.getCause(); + } + + return csc.createResult(exitCode, new ProcessAttributes(getPID(process), pb.command())); + } + + private Result execute(ToolProvider tp, long timeoutMillis, String... args) + throws IOException, InterruptedException { + + var csc = new CachingStreamsConfig(); + + Optional exitCode; + var out = csc.out(); + var err = csc.err(); + try { + if (timeoutMillis < 0) { + exitCode = Optional.of(tp.run(out, err, args)); + } else { + var future = new CompletableFuture>(); + + var workerThread = Thread.ofVirtual().start(() -> { + Optional result = Optional.empty(); + try { + result = Optional.of(tp.run(out, err, args)); + } catch (Exception ex) { + future.completeExceptionally(ex); + return; + } + future.complete(result); + }); + + try { + exitCode = future.get(timeoutMillis, TimeUnit.MILLISECONDS); + } catch (ExecutionException ex) { + // Rethrow the cause (ex.getCause()) as a RuntimeException. + // If `ex.getCause()` returns an Error, ExceptionBox.unbox() will throw it. + throw ExceptionBox.toUnchecked(ExceptionBox.unbox(ex.getCause())); + } catch (TimeoutException ex) { + workerThread.interrupt(); + exitCode = Optional.empty(); + } + } + } finally { + suppressIOException(out::flush); + suppressIOException(err::flush); + } + + return csc.createResult(exitCode, new ToolProviderAttributes(tp.name(), List.of(args))); + } + + private CommandOutputControl setOutputControl(boolean set, OutputControlOption v) { + outputStreamsControl.stdout().set(set, v); + outputStreamsControl.stderr().set(set, v); + return this; + } + + private CommandOutputControl setFlag(Flag flag, boolean v) { + flags = flag.set(flags, v); + return this; + } + + private Optional streamFileSink(ProcessBuilder.Redirect redirect) { + return Optional.of(redirect) + .filter(Predicate.isEqual(ProcessBuilder.Redirect.DISCARD).negate()) + .map(ProcessBuilder.Redirect::file) + .map(File::toPath); + } + + private void configureProcessBuilder(ProcessBuilder pb) throws IOException { + + var stdoutRedirect = outputStreamsControl.stdout().asProcessBuilderRedirect(); + var stderrRedirect = outputStreamsControl.stderr().asProcessBuilderRedirect(); + + if (!stdoutRedirect.equals(stderrRedirect) && Stream.of( + stdoutRedirect, + stderrRedirect + ).noneMatch(Predicate.isEqual(ProcessBuilder.Redirect.DISCARD)) && redirectRetainedStderr()) { + throw new IllegalStateException(String.format( + "Can't redirect stderr into stdout because they have different redirects: stdout=%s; stderr=%s", + stdoutRedirect, stderrRedirect)); + } + + pb.redirectErrorStream(redirectRetainedStderr()); + if (replaceStdoutWithStderr()) { + if (stderrRedirect.equals(ProcessBuilder.Redirect.INHERIT)) { + stderrRedirect = ProcessBuilder.Redirect.PIPE; + } + pb.redirectErrorStream(false); + } + + stdoutRedirect = mapRedirect(stdoutRedirect); + stderrRedirect = mapRedirect(stderrRedirect); + + if (dumpStdout != null && stdoutRedirect.equals(ProcessBuilder.Redirect.INHERIT)) { + stdoutRedirect = ProcessBuilder.Redirect.PIPE; + } + + if (dumpStderr != null && stderrRedirect.equals(ProcessBuilder.Redirect.INHERIT)) { + stderrRedirect = ProcessBuilder.Redirect.PIPE; + } + + pb.redirectOutput(stdoutRedirect); + pb.redirectError(stderrRedirect); + } + + private ProcessBuilder.Redirect mapRedirect(ProcessBuilder.Redirect redirect) throws IOException { + if (isStoreOutputInFiles() && redirect.equals(ProcessBuilder.Redirect.PIPE)) { + var sink = Files.createTempFile("jpackageOutputTempFile", ".tmp"); + return ProcessBuilder.Redirect.to(sink.toFile()); + } else { + return redirect; + } + } + + /** + * Returns {@code true} if STDERR is not discarded and will be redirected to STDOUT, and {@code false} otherwise. + */ + private boolean redirectRetainedStderr() { + return isRedirectStderr() && !outputStreamsControl.stderr().discard(); + } + + /** + * Returns {@code true} if STDERR will replace STDOUT, and {@code false} otherwise. + *

    + * STDERR will replace STDOUT if it is redirected and not discarded, and if STDOUT is discarded. + */ + private boolean replaceStdoutWithStderr() { + return redirectRetainedStderr() && outputStreamsControl.stdout().discard(); + } + + private static T join(CompletableFuture future, T cancelledValue) { + Objects.requireNonNull(future); + try { + return future.join(); + } catch (CancellationException ex) { + return cancelledValue; + } catch (CompletionException ex) { + switch (ExceptionBox.unbox(ex.getCause())) { + case IOException cause -> { + throw new UncheckedIOException(cause); + } + case UncheckedIOException cause -> { + throw cause; + } + case Exception cause -> { + throw ExceptionBox.toUnchecked(cause); + } + } + } + } + + private static void join(CompletableFuture future) { + join(future, null); + } + + private static boolean mustReadOutputStream(ProcessBuilder.Redirect redirect) { + return redirect.equals(ProcessBuilder.Redirect.PIPE); + } + + private static Optional> read(OutputControl outputControl, CachingPrintStream cps) throws IOException { + final var bufferAsString = cps.bufferContents(); + try (final var bufReader = new BufferedReader(new StringReader(bufferAsString.orElse("")))) { + if (outputControl.saveFirstLine()) { + return Optional.of(bufReader.lines().findFirst().map(List::of).orElseGet(List::of)); + } else if (outputControl.saveAll()) { + return Optional.of(bufReader.lines().toList()); + } else { + return Optional.empty(); + } + } catch (UncheckedIOException ex) { + throw ex.getCause(); + } + } + + private static Optional readBinary(OutputControl outputControl, CachingPrintStream cps) { + if (outputControl.save()) { + return cps.buf().map(ByteArrayOutputStream::toByteArray).or(() -> { + return Optional.of(new byte[0]); + }); + } else { + return Optional.empty(); + } + } + + private static CommandOutput combine( + Optional> out, + Optional> err, + boolean interleaved) { + + if (out.isEmpty() && err.isEmpty()) { + return CommandOutput.empty(); + } else if (out.isEmpty()) { + // This branch is unreachable because it is impossible to make it save stderr without saving stdout. + // If streams are configured for saving and stdout is discarded, + // its saved contents will be an Optional instance wrapping an empty content, not an empty Optional. + throw ExceptionBox.reachedUnreachable(); + } else if (err.isEmpty()) { + return new CommandOutput<>(out, Integer.MAX_VALUE, interleaved); + } else { + final var combined = out.get().append(err.get()); + return new CommandOutput<>(Optional.of(combined), out.orElseThrow().size(), interleaved); + } + } + + private static PrintStream nullPrintStream() { + return new PrintStream(OutputStream.nullOutputStream()); + } + + private sealed interface Content { + T data(); + int size(); + Content slice(int from, int to); + Content append(Content other); + } + + private record StringListContent(List data) implements Content> { + StringListContent { + Objects.requireNonNull(data); + } + + @Override + public int size() { + return data.size(); + } + + @Override + public StringListContent slice(int from, int to) { + return new StringListContent(data.subList(from, to)); + } + + @Override + public StringListContent append(Content> other) { + return new StringListContent(Stream.of(data, other.data()).flatMap(List::stream).toList()); + } + } + + private record ByteContent(byte[] data) implements Content { + ByteContent { + Objects.requireNonNull(data); + } + + @Override + public int size() { + return data.length; + } + + @Override + public ByteContent slice(int from, int to) { + return new ByteContent(Arrays.copyOfRange(data, from, to)); + } + + @Override + public ByteContent append(Content other) { + byte[] combined = new byte[size() + other.size()]; + System.arraycopy(data, 0, combined, 0, data.length); + System.arraycopy(other.data(), 0, combined, data.length, other.size()); + return new ByteContent(combined); + } + } + + private record OutputStreamsControl(OutputControl stdout, OutputControl stderr) { + OutputStreamsControl { + Objects.requireNonNull(stdout); + Objects.requireNonNull(stderr); + } + + OutputStreamsControl() { + this(new OutputControl(), new OutputControl()); + } + + OutputStreamsControl copy() { + return new OutputStreamsControl(stdout.copy(), stderr.copy()); + } + + List descriptionTokens() { + final List tokens = new ArrayList<>(); + if (stdout.save()) { // Save flags are the same for stdout and stderr, checking stdout is sufficient. + streamsLabel("save ", true).ifPresent(tokens::add); + } + if (stdout.dump() || stderr.dump()) { + streamsLabel("echo ", true).ifPresent(tokens::add); + } + streamsLabel("discard ", false).ifPresent(tokens::add); + if (tokens.isEmpty()) { + // Unreachable because there is always at least one token in the description. + throw ExceptionBox.reachedUnreachable(); + } else { + return tokens; + } + } + + private Optional streamsLabel(String prefix, boolean negate) { + Objects.requireNonNull(prefix); + final var str = Stream.of(stdoutLabel(negate), stderrLabel(negate)) + .filter(Optional::isPresent) + .map(Optional::orElseThrow) + .collect(joining("+")); + if (str.isEmpty()) { + return Optional.empty(); + } else { + return Optional.of(prefix + str); + } + } + + private Optional stdoutLabel(boolean negate) { + if ((stdout.discard() && !negate) || (!stdout.discard() && negate)) { + return Optional.of("out"); + } else { + return Optional.empty(); + } + } + + private Optional stderrLabel(boolean negate) { + if ((stderr.discard() && !negate) || (!stderr.discard() && negate)) { + return Optional.of("err"); + } else { + return Optional.empty(); + } + } + } + + private record CachingPrintStream(PrintStream ps, Optional buf) { + CachingPrintStream { + Objects.requireNonNull(ps); + Objects.requireNonNull(buf); + } + + Optional bufferContents() { + return buf.map(ByteArrayOutputStream::toString); + } + + static Builder build(Charset charset) { + return new Builder(charset); + } + + static final class Builder { + + private Builder(Charset charset) { + this.charset = Objects.requireNonNull(charset); + } + + Builder save(boolean v) { + save = v; + return this; + } + + Builder discard(boolean v) { + discard = v; + return this; + } + + Builder dumpStream(PrintStream v) { + dumpStream = v; + return this; + } + + Builder buffer(ByteArrayOutputStream v) { + externalBuffer = v; + return this; + } + + CachingPrintStream create() { + final Optional buf; + if (save && !discard) { + buf = Optional.ofNullable(externalBuffer).or(() -> { + return Optional.of(new ByteArrayOutputStream()); + }); + } else { + buf = Optional.empty(); + } + + final PrintStream ps; + if (buf.isPresent() && dumpStream != null) { + ps = new PrintStream(new TeeOutputStream(List.of(buf.get(), dumpStream)), true, dumpStream.charset()); + } else if (!discard) { + ps = buf.map(in -> { + return new PrintStream(in, false, charset); + }).or(() -> { + return Optional.ofNullable(dumpStream); + }).orElseGet(CommandOutputControl::nullPrintStream); + } else { + ps = nullPrintStream(); + } + + return new CachingPrintStream(ps, buf); + } + + private boolean save; + private boolean discard; + private PrintStream dumpStream; + private ByteArrayOutputStream externalBuffer; + private final Charset charset; + } + } + + private final class CachingStreamsConfig { + + CachingStreamsConfig() { + out = outputStreamsControl.stdout().buildCachingPrintStream(dumpStdout(), charset()).create(); + if (isRedirectStderr()) { + var builder = outputStreamsControl.stderr().buildCachingPrintStream(dumpStdout(), charset()); + out.buf().ifPresent(builder::buffer); + err = builder.create(); + } else { + err = outputStreamsControl.stderr().buildCachingPrintStream(dumpStderr(), charset()).create(); + } + } + + Result createResult(Optional exitCode, ExecutableAttributes execAttrs) throws IOException { + + CommandOutput> output; + CommandOutput byteOutput; + + CachingPrintStream effectiveOut; + if (out.buf().isEmpty() && isRedirectStderr()) { + effectiveOut = new CachingPrintStream(nullPrintStream(), err.buf()); + } else { + effectiveOut = out; + } + + if (isBinaryOutput()) { + Optional outContent, errContent; + if (isRedirectStderr()) { + outContent = readBinary(outputStreamsControl.stdout(), effectiveOut).map(ByteContent::new); + errContent = Optional.empty(); + } else { + outContent = readBinary(outputStreamsControl.stdout(), out).map(ByteContent::new); + errContent = readBinary(outputStreamsControl.stderr(), err).map(ByteContent::new); + } + + byteOutput = combine(outContent, errContent, redirectRetainedStderr()); + output = null; + } else { + Optional outContent, errContent; + if (isRedirectStderr()) { + outContent = read(outputStreamsControl.stdout(), effectiveOut).map(StringListContent::new); + errContent = Optional.empty(); + } else { + outContent = read(outputStreamsControl.stdout(), out).map(StringListContent::new); + errContent = read(outputStreamsControl.stderr(), err).map(StringListContent::new); + } + + output = combine(outContent, errContent, redirectRetainedStderr()); + byteOutput = null; + } + + return new Result(exitCode, Optional.ofNullable(output), Optional.ofNullable(byteOutput), execAttrs); + } + + PrintStream out() { + return out.ps(); + } + + PrintStream err() { + return err.ps(); + } + + private final CachingPrintStream out; + private final CachingPrintStream err; + } + + private static final class OutputControl { + + OutputControl() { + } + + private OutputControl(OutputControl other) { + dump = other.dump; + discard = other.discard; + save = other.save; + } + + boolean save() { + return save.isPresent(); + } + + boolean saveAll() { + return save.orElse(null) == OutputControlOption.SAVE_ALL; + } + + boolean saveFirstLine() { + return save.orElse(null) == OutputControlOption.SAVE_FIRST_LINE; + } + + boolean discard() { + return discard || (!dump && save.isEmpty()); + } + + boolean dump() { + return !discard && dump; + } + + OutputControl dump(boolean v) { + this.dump = v; + return this; + } + + OutputControl discard(boolean v) { + this.discard = v; + return this; + } + + OutputControl saveAll(boolean v) { + if (v) { + save = Optional.of(OutputControlOption.SAVE_ALL); + } else { + save = Optional.empty(); + } + return this; + } + + OutputControl saveFirstLine(boolean v) { + if (v) { + save = Optional.of(OutputControlOption.SAVE_FIRST_LINE); + } else { + save = Optional.empty(); + } + return this; + } + + OutputControl set(boolean set, OutputControlOption v) { + switch (v) { + case DUMP -> dump(set); + case SAVE_ALL -> saveAll(set); + case SAVE_FIRST_LINE -> saveFirstLine(set); + } + return this; + } + + OutputControl copy() { + return new OutputControl(this); + } + + ProcessBuilder.Redirect asProcessBuilderRedirect() { + if (discard()) { + return ProcessBuilder.Redirect.DISCARD; + } else if (dump && !save()) { + return ProcessBuilder.Redirect.INHERIT; + } else { + return ProcessBuilder.Redirect.PIPE; + } + } + + CachingPrintStream.Builder buildCachingPrintStream(PrintStream dumpStream, Charset charset) { + Objects.requireNonNull(dumpStream); + final var builder = CachingPrintStream.build(charset).save(save()).discard(discard()); + if (dump()) { + builder.dumpStream(dumpStream); + } + return builder; + } + + private boolean dump; + private boolean discard; + private Optional save = Optional.empty(); + } + + private record CommandOutput(Optional> content, int stdoutContentSize, boolean interleaved) { + + CommandOutput { + Objects.requireNonNull(content); + if (interleaved) { + stdoutContentSize = content.map(Content::size).orElse(-1); + } + } + + CommandOutput() { + this(Optional.empty(), 0, false); + } + + Optional combined() { + return content.map(Content::data); + } + + /** + * Returns non-empty {@code Optional} if stdout is available and stdout and stderr are not interleaved. + * @return stdout if it can be extracted from the combined output + */ + Optional stdout() { + if (withoutExtractableStdout()) { + return Optional.empty(); + } + + final var theContent = content.orElseThrow(); + if (stdoutContentSize == theContent.size()) { + return combined(); + } else { + return Optional.of(theContent.slice(0, Integer.min(stdoutContentSize, theContent.size())).data()); + } + } + + /** + * Returns non-empty {@code Optional} if stderr is available and stdout and stderr are not interleaved. + * @return stderr if it can be extracted from the combined output + */ + Optional stderr() { + if (withoutExtractableStderr()) { + return Optional.empty(); + } else if (stdoutContentSize <= 0) { + return combined(); + } else { + final var theContent = content.orElseThrow(); + return Optional.of(theContent.slice(stdoutContentSize, theContent.size()).data()); + } + } + + @SuppressWarnings("unchecked") + static CommandOutput empty() { + return (CommandOutput)EMPTY; + } + + private boolean withoutExtractableStdout() { + return interleaved || content.isEmpty() || stdoutContentSize < 0; + } + + private boolean withoutExtractableStderr() { + return interleaved || content.isEmpty() || stdoutContentSize > content.get().size(); + } + + private static final CommandOutput EMPTY = new CommandOutput<>(); + } + + private record ToolProviderExecutable(ToolProvider tp, List args, CommandOutputControl coc) implements Executable { + + ToolProviderExecutable { + Objects.requireNonNull(tp); + Objects.requireNonNull(args); + Objects.requireNonNull(coc); + } + + @Override + public Result execute() throws IOException, InterruptedException { + return coc.execute(tp, -1, args.toArray(String[]::new)); + } + + @Override + public Result execute(long timeout, TimeUnit unit) throws IOException, InterruptedException { + return coc.execute(tp, unit.toMillis(timeout), args.toArray(String[]::new)); + } + + @Override + public ExecutableAttributes attributes() { + return new ToolProviderAttributes(tp.name(), args); + } + } + + private record ProcessExecutable(ProcessBuilder pb, CommandOutputControl coc) implements Executable { + + ProcessExecutable { + Objects.requireNonNull(pb); + Objects.requireNonNull(coc); + } + + @Override + public Result execute() throws IOException, InterruptedException { + return coc.execute(pb, -1L); + } + + @Override + public Result execute(long timeout, TimeUnit unit) throws IOException, InterruptedException { + return coc.execute(pb, unit.toMillis(timeout)); + } + + @Override + public ExecutableAttributes attributes() { + return new ProcessAttributes(Optional.empty(), pb.command()); + } + } + + private static Optional getPID(Process p) { + try { + return Optional.of(p.pid()); + } catch (UnsupportedOperationException ex) { + return Optional.empty(); + } + } + + private static void suppressIOException(ThrowingRunnable r) { + try { + r.run(); + } catch (IOException ex) {} + } + + private static void suppressIOException(ThrowingConsumer c, T value) { + suppressIOException(() -> { + c.accept(value); + }); + } + + private int flags; + private final OutputStreamsControl outputStreamsControl; + private PrintStream dumpStdout; + private PrintStream dumpStderr; + private Charset charset; + private Consumer processListener; + + // Executor to run subprocess output stream gobblers. + // Output stream gobblers should start fetching output streams ASAP after the process starts. + // No pooling, no waiting. + // CompletableFuture#runAsync() method starts an output stream gobbler. + // If used with the default executor, it is known to make WiX3 light.exe create + // a locked msi file when multiple jpackage tool providers are executed asynchronously. + // The AsyncTest fails with cryptic java.nio.file.FileSystemException error: + // jtreg_open_test_jdk_tools_jpackage_share_AsyncTest_java\\tmp\\jdk.jpackage8108811639097525318\\msi\\Foo-1.0.msi: The process cannot access the file because it is being used by another process. + // The remedy for the problem is to use non-pooling executor to run subprocess output stream gobblers. + private final java.util.concurrent.Executor gobblerExecutor = Executors.newVirtualThreadPerTaskExecutor(); + + private enum OutputControlOption { + SAVE_ALL, SAVE_FIRST_LINE, DUMP + } + + private enum Flag { + DUMP (0x01), + REDIRECT_STDERR (0x02), + BINARY_OUTPUT (0x04), + STORE_OUTPUT_IN_FILES (0x08), + DISCARD_STDOUT (0x10), + DISCARD_STDERR (0x20), + ; + + Flag(int value) { + this.value = value; + } + + int set(int flags, boolean set) { + if (set) { + return flags | value; + } else { + return flags & ~value; + } + } + + boolean isSet(int flags) { + return (flags & value) != 0; + } + + private final int value; + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Enquoter.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/Enquoter.java similarity index 77% rename from src/jdk.jpackage/share/classes/jdk/jpackage/internal/Enquoter.java rename to src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/Enquoter.java index 51f97ad2cd6..d9ededcbadb 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Enquoter.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/Enquoter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package jdk.jpackage.internal; +package jdk.jpackage.internal.util; import java.util.Optional; import java.util.function.BiConsumer; @@ -32,39 +32,43 @@ import java.util.regex.Pattern; /** * Add quotes to the given string in a configurable way. */ -final class Enquoter { +public final class Enquoter { private Enquoter() { setQuoteChar('"'); } - static Enquoter forPropertyValues() { + public static Enquoter identity() { + return new Enquoter(); + } + + public static Enquoter forPropertyValues() { return new Enquoter() .setEnquotePredicate(QUOTE_IF_WHITESPACES) .setEscaper(PREPEND_BACKSLASH); } - static Enquoter forShellLiterals() { + public static Enquoter forShellLiterals() { return forShellLiterals('\''); } - static Enquoter forShellLiterals(char quoteChar) { + public static Enquoter forShellLiterals(char quoteChar) { return new Enquoter() .setQuoteChar(quoteChar) .setEnquotePredicate(x -> true) .setEscaper(PREPEND_BACKSLASH); } - String applyTo(String v) { + public String applyTo(String v) { if (!needQuotes.test(v)) { return v; } else { var buf = new StringBuilder(); buf.appendCodePoint(beginQuoteChr); - Optional.of(escaper).ifPresentOrElse(op -> { + Optional.ofNullable(escaper).ifPresentOrElse(op -> { v.codePoints().forEachOrdered(chr -> { if (chr == beginQuoteChr || chr == endQuoteChr) { - escaper.accept(chr, buf); + op.accept(chr, buf); } else { buf.appendCodePoint(chr); } @@ -77,28 +81,23 @@ final class Enquoter { } } - Enquoter setQuoteChar(char chr) { + public Enquoter setQuoteChar(char chr) { beginQuoteChr = chr; endQuoteChr = chr; return this; } - Enquoter setEscaper(BiConsumer v) { + public Enquoter setEscaper(BiConsumer v) { escaper = v; return this; } - Enquoter setEnquotePredicate(Predicate v) { + public Enquoter setEnquotePredicate(Predicate v) { needQuotes = v; return this; } - private int beginQuoteChr; - private int endQuoteChr; - private BiConsumer escaper; - private Predicate needQuotes = str -> false; - - private static final Predicate QUOTE_IF_WHITESPACES = new Predicate() { + public static final Predicate QUOTE_IF_WHITESPACES = new Predicate() { @Override public boolean test(String t) { return pattern.matcher(t).find(); @@ -106,8 +105,13 @@ final class Enquoter { private final Pattern pattern = Pattern.compile("\\s"); }; - private static final BiConsumer PREPEND_BACKSLASH = (chr, buf) -> { + public static final BiConsumer PREPEND_BACKSLASH = (chr, buf) -> { buf.append('\\'); buf.appendCodePoint(chr); }; + + private int beginQuoteChr; + private int endQuoteChr; + private BiConsumer escaper; + private Predicate needQuotes = str -> false; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/FileUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/FileUtils.java index 2f7f2682a71..b143a54cb5c 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/FileUtils.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/FileUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -57,12 +57,6 @@ public final class FileUtils { public static void copyRecursive(Path src, Path dest, CopyOption... options) throws IOException { - copyRecursive(src, dest, List.of(), options); - } - - public static void copyRecursive(Path src, Path dest, - final List excludes, CopyOption... options) - throws IOException { List copyActions = new ArrayList<>(); @@ -71,24 +65,18 @@ public final class FileUtils { @Override public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) { - if (isPathMatch(dir, excludes)) { - return FileVisitResult.SKIP_SUBTREE; - } else { - copyActions.add(new CopyAction(null, dest.resolve(src.relativize(dir)))); - return FileVisitResult.CONTINUE; - } + copyActions.add(new CopyAction(null, dest.resolve(src.relativize(dir)))); + return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) { - if (!isPathMatch(file, excludes)) { - copyActions.add(new CopyAction(file, dest.resolve(src.relativize(file)))); - } + copyActions.add(new CopyAction(file, dest.resolve(src.relativize(file)))); return FileVisitResult.CONTINUE; } }); - } else if (!isPathMatch(src, excludes)) { + } else { Optional.ofNullable(dest.getParent()).ifPresent(dstDir -> { copyActions.add(new CopyAction(null, dstDir)); }); @@ -113,10 +101,6 @@ public final class FileUtils { } } - private static boolean isPathMatch(Path what, List paths) { - return paths.stream().anyMatch(what::endsWith); - } - private static record CopyAction(Path src, Path dest) { void apply(CopyOption... options) throws IOException { diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/IdentityWrapper.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/IdentityWrapper.java index 0a7a8cc8d4b..7b4c75630ac 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/IdentityWrapper.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/IdentityWrapper.java @@ -69,5 +69,9 @@ public final class IdentityWrapper { return String.format("Identity[%s]", value); } + public static IdentityWrapper wrapIdentity(T v) { + return new IdentityWrapper<>(v); + } + private final T value; } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundle.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/MacBundle.java similarity index 63% rename from src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundle.java rename to src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/MacBundle.java index 723614f9bd6..95629e7d4b5 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundle.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/MacBundle.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 @@ -23,54 +23,49 @@ * questions. */ -package jdk.jpackage.internal; +package jdk.jpackage.internal.util; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; import java.util.Optional; -import jdk.jpackage.internal.model.AppImageLayout; /** * An abstraction of macOS Application bundle. * * @see https://en.wikipedia.org/wiki/Bundle_(macOS)#Application_bundles */ -record MacBundle(Path root) { +public record MacBundle(Path root) { - MacBundle { + public MacBundle { Objects.requireNonNull(root); } - boolean isValid() { + public boolean isValid() { return Files.isDirectory(contentsDir()) && Files.isDirectory(macOsDir()) && Files.isRegularFile(infoPlistFile()); } - boolean isSigned() { - return Files.isDirectory(contentsDir().resolve("_CodeSignature")); - } - - Path contentsDir() { + public Path contentsDir() { return root.resolve("Contents"); } - Path homeDir() { + public Path homeDir() { return contentsDir().resolve("Home"); } - Path macOsDir() { + public Path macOsDir() { return contentsDir().resolve("MacOS"); } - Path resourcesDir() { + public Path resourcesDir() { return contentsDir().resolve("Resources"); } - Path infoPlistFile() { + public Path infoPlistFile() { return contentsDir().resolve("Info.plist"); } - static Optional fromPath(Path path) { + public static Optional fromPath(Path path) { var bundle = new MacBundle(path); if (bundle.isValid()) { return Optional.of(bundle); @@ -78,20 +73,4 @@ record MacBundle(Path root) { return Optional.empty(); } } - - static Optional fromAppImageLayout(AppImageLayout layout) { - final var root = layout.rootDirectory(); - final var bundleSubdir = root.relativize(layout.runtimeDirectory()); - final var contentsDirname = Path.of("Contents"); - var bundleRoot = root; - for (int i = 0; i != bundleSubdir.getNameCount(); i++) { - var nameComponent = bundleSubdir.getName(i); - if (contentsDirname.equals(nameComponent)) { - return Optional.of(new MacBundle(bundleRoot)); - } else { - bundleRoot = bundleRoot.resolve(nameComponent); - } - } - return Optional.empty(); - } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PListReader.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PListReader.java index 2c693939dab..4c85358d424 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PListReader.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PListReader.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 @@ -35,7 +35,6 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; -import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.dom.DOMSource; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; @@ -311,7 +310,7 @@ public final class PListReader { } } - public PListReader(byte[] xmlData) throws ParserConfigurationException, SAXException, IOException { + public PListReader(byte[] xmlData) throws SAXException, IOException { this(XmlUtils.initDocumentBuilder().parse(new ByteArrayInputStream(xmlData))); } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/RetryExecutor.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/RetryExecutor.java new file mode 100644 index 00000000000..c67373e8233 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/RetryExecutor.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2020, 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.util; + +import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; + +import java.time.Duration; +import java.util.Iterator; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Function; +import jdk.jpackage.internal.util.function.ExceptionBox; +import jdk.jpackage.internal.util.function.ThrowingFunction; +import jdk.jpackage.internal.util.function.ThrowingSupplier; + +public class RetryExecutor { + + public RetryExecutor(Class exceptionType) { + this.exceptionType = Objects.requireNonNull(exceptionType); + setMaxAttemptsCount(5); + setAttemptTimeout(2, TimeUnit.SECONDS); + } + + final public Class exceptionType() { + return exceptionType; + } + + public RetryExecutor setExecutable(ThrowingFunction>, T, E> v) { + executable = v; + return this; + } + + final public RetryExecutor setExecutable(ThrowingSupplier v) { + if (v != null) { + setExecutable(_ -> { + return v.get(); + }); + } else { + executable = null; + } + return this; + } + + public RetryExecutor setMaxAttemptsCount(int v) { + attempts = v; + return this; + } + + final public RetryExecutor setAttemptTimeout(long v, TimeUnit unit) { + return setAttemptTimeout(Duration.of(v, unit.toChronoUnit())); + } + + public RetryExecutor setAttemptTimeout(Duration v) { + timeout = v; + return this; + } + + public RetryExecutor setExceptionMapper(Function v) { + toUnchecked = v; + return this; + } + + public RetryExecutor setSleepFunction(Consumer v) { + sleepFunction = v; + return this; + } + + final public RetryExecutor mutate(Consumer> mutator) { + mutator.accept(this); + return this; + } + + public T execute() throws E { + var curExecutable = executable(); + T result = null; + var attemptIter = new DefaultContext(); + while (attemptIter.hasNext()) { + attemptIter.next(); + try { + result = curExecutable.apply(attemptIter); + break; + } catch (Exception ex) { + if (!exceptionType.isInstance(ex)) { + throw ExceptionBox.toUnchecked(ex); + } else if (attemptIter.isLastAttempt()) { + // No more attempts left. This is fatal. + throw exceptionType.cast(ex); + } else { + curExecutable = executable(); + } + } + + sleep(); + } + + return result; + } + + final public T executeUnchecked() { + try { + return execute(); + } catch (Error | RuntimeException t) { + throw t; + } catch (Exception ex) { + if (exceptionType.isInstance(ex)) { + throw Optional.ofNullable(toUnchecked).orElse(ExceptionBox::toUnchecked).apply(exceptionType.cast(ex)); + } else { + // Unreachable unless it is a direct subclass of Throwable, + // which is not Error or Exception which should not happen. + throw ExceptionBox.reachedUnreachable(); + } + } + } + + public interface Context { + boolean isLastAttempt(); + int attempt(); + T executor(); + } + + private final class DefaultContext implements Context>, Iterator { + + @Override + public boolean isLastAttempt() { + return !hasNext(); + } + + @Override + public int attempt() { + return attempt; + } + + @Override + public boolean hasNext() { + return (attempts - attempt) > 1; + } + + @Override + public Void next() { + attempt++; + return null; + } + + @Override + public RetryExecutor executor() { + return RetryExecutor.this; + } + + private int attempt = -1; + } + + private ThrowingFunction>, T, E> executable() { + return Optional.ofNullable(executable).orElseThrow(() -> { + return new IllegalStateException("No executable"); + }); + } + + private void sleep() { + Optional.ofNullable(timeout).ifPresent(Optional.ofNullable(sleepFunction).orElseGet(() -> { + return toConsumer(Thread::sleep); + })); + } + + private final Class exceptionType; + private ThrowingFunction>, T, E> executable; + private int attempts; + private Duration timeout; + private Function toUnchecked; + private Consumer sleepFunction; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/RootedPath.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/RootedPath.java new file mode 100644 index 00000000000..21d3b6b17b0 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/RootedPath.java @@ -0,0 +1,185 @@ +/* + * 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 + * 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.util; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.CopyOption; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * A relative path (branch) rooted at the root. + */ +public sealed interface RootedPath { + + Path root(); + Path branch(); + + default Path fullPath() { + return root().resolve(branch()); + } + + public static Function toRootedPath(Path root) { + return path -> { + if (!path.startsWith(root)) { + throw new IllegalArgumentException(String.format("Expected path [%s] to start with [%s] root", path, root)); + } + return new Details.DefaultRootedPath(root, root.relativize(path), Files.isDirectory(path)); + }; + } + + public static void copy(Stream rootedPaths, Path dest, CopyOption...options) throws IOException { + Objects.requireNonNull(rootedPaths); + Objects.requireNonNull(dest); + + var marks = new HashMap(); + + // Preset marks for the preexisting paths. + Files.walkFileTree(dest, new FileVisitor() { + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + marks.put(dir, new Details.PathMark(true, true)); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + marks.put(file, new Details.PathMark(false, true)); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + return FileVisitResult.TERMINATE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + + }); + + var replacePreexisting = Set.of(options).contains(StandardCopyOption.REPLACE_EXISTING); + + Predicate canReplace = v -> { + return v.isPreexisting() && replacePreexisting; + }; + + try { + rootedPaths.sequential().map(Details.DefaultRootedPath.class::cast).forEach(rootedPath -> { + + final var dstPath = dest.resolve(rootedPath.branch()); + + var dstPathMark = marks.get(dstPath); + + if (!Optional.ofNullable(dstPathMark).map(canReplace::test).orElse(true)) { + // Destination path can not be replaced, bail out. + return; + } + + // Check the ancestors of the destination path. + for (var ancestor : Details.ancestorPaths(rootedPath.branch())) { + var mark = Optional.ofNullable(marks.get(dest.resolve(ancestor))); + + if (!mark.map(Details.PathMark::isDirectory).orElse(true)) { + // `ancestor` is a file, don't overwrite it. + return; + } + } + + dstPathMark = rootedPath.createPathMark(); + marks.put(dstPath, dstPathMark); + + try { + if (replacePreexisting && (rootedPath.isDirectory() != dstPathMark.isDirectory())) { + FileUtils.deleteRecursive(dstPath); + } + + if (rootedPath.isDirectory()) { + Files.createDirectories(dstPath); + } else { + Files.createDirectories(dstPath.getParent()); + Files.copy(rootedPath.fullPath(), dstPath, options); + } + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }); + } catch (UncheckedIOException ex) { + throw ex.getCause(); + } + } + + static final class Details { + + private Details() { + } + + private record DefaultRootedPath(Path root, Path branch, boolean isDirectory) implements RootedPath { + + DefaultRootedPath { + Objects.requireNonNull(root); + Objects.requireNonNull(branch); + if (branch.isAbsolute()) { + throw new IllegalArgumentException(); + } + } + + PathMark createPathMark() { + return new PathMark(isDirectory); + } + } + + private record PathMark(boolean isDirectory, boolean isPreexisting) { + PathMark(boolean isDirectory) { + this(isDirectory, false); + } + } + + private static List ancestorPaths(Path path) { + var ancestors = new ArrayList(); + while ((path = path.getParent()) != null) { + ancestors.add(path); + } + return ancestors; + } + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/SetBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/SetBuilder.java index 18736b4ed7e..d3d677cba50 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/SetBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/SetBuilder.java @@ -31,7 +31,7 @@ import java.util.Set; public final class SetBuilder { - public static SetBuilder build(Class type) { + public static SetBuilder build() { return new SetBuilder<>(); } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/TeeOutputStream.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/TeeOutputStream.java new file mode 100644 index 00000000000..db098eb2b48 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/TeeOutputStream.java @@ -0,0 +1,89 @@ +/* + * 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.util; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Objects; +import jdk.jpackage.internal.util.function.ThrowingConsumer; + +public final class TeeOutputStream extends OutputStream { + + public TeeOutputStream(Iterable items) { + items.forEach(Objects::requireNonNull); + this.items = items; + } + + @Override + public void write(int b) throws IOException { + for (final var item : items) { + item.write(b); + } + } + + @Override + public void write(byte[] b) throws IOException { + for (final var item : items) { + item.write(b); + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + for (final var item : items) { + item.write(b, off, len); + } + } + + @Override + public void flush() throws IOException { + forEach(Flushable::flush); + } + + @Override + public void close() throws IOException { + forEach(Closeable::close); + } + + private void forEach(ThrowingConsumer c) throws IOException { + IOException firstEx = null; + for (final var item : items) { + try { + c.accept(item); + } catch (IOException e) { + if (firstEx == null) { + firstEx = e; + } + } + } + if (firstEx != null) { + throw firstEx; + } + } + + private final Iterable items; +} diff --git a/src/jdk.jpackage/share/native/applauncher/PackageFile.cpp b/src/jdk.jpackage/share/native/applauncher/PackageFile.cpp index 31b431215d5..4bf51348d6d 100644 --- a/src/jdk.jpackage/share/native/applauncher/PackageFile.cpp +++ b/src/jdk.jpackage/share/native/applauncher/PackageFile.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 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/src/jdk.jpackage/share/native/common/Dll.h b/src/jdk.jpackage/share/native/common/Dll.h index c84242373a7..bfe9e25238e 100644 --- a/src/jdk.jpackage/share/native/common/Dll.h +++ b/src/jdk.jpackage/share/native/common/Dll.h @@ -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/src/jdk.jpackage/share/native/common/app.cpp b/src/jdk.jpackage/share/native/common/app.cpp index d9492785f06..a64e84b70fb 100644 --- a/src/jdk.jpackage/share/native/common/app.cpp +++ b/src/jdk.jpackage/share/native/common/app.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, 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/src/jdk.jpackage/share/native/common/tstrings.cpp b/src/jdk.jpackage/share/native/common/tstrings.cpp index b38c811e8bd..7ceb87edfa1 100644 --- a/src/jdk.jpackage/share/native/common/tstrings.cpp +++ b/src/jdk.jpackage/share/native/common/tstrings.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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/jdk.jpackage/unix/classes/jdk/jpackage/internal/UnixLaunchersAsServices.java b/src/jdk.jpackage/unix/classes/jdk/jpackage/internal/UnixLaunchersAsServices.java index e1e02ba7850..b196a5805a0 100644 --- a/src/jdk.jpackage/unix/classes/jdk/jpackage/internal/UnixLaunchersAsServices.java +++ b/src/jdk.jpackage/unix/classes/jdk/jpackage/internal/UnixLaunchersAsServices.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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,8 +24,6 @@ */ package jdk.jpackage.internal; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.Application; import java.io.IOException; import java.nio.file.Path; import java.util.Collections; @@ -36,6 +34,9 @@ import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import jdk.jpackage.internal.model.Application; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.util.Enquoter; /** * Helper to install launchers as services for Unix installers. diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinBundlingEnvironment.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinBundlingEnvironment.java index 38547999290..de52a222d7d 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinBundlingEnvironment.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinBundlingEnvironment.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,29 +24,30 @@ */ package jdk.jpackage.internal; -import static jdk.jpackage.internal.WinFromOpions.createWinApplication; +import static jdk.jpackage.internal.WinFromOptions.createWinApplication; import static jdk.jpackage.internal.WinPackagingPipeline.APPLICATION_LAYOUT; import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_WIN_APP_IMAGE; import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_WIN_EXE; import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_WIN_MSI; import jdk.jpackage.internal.cli.Options; -import jdk.jpackage.internal.util.Result; public class WinBundlingEnvironment extends DefaultBundlingEnvironment { public WinBundlingEnvironment() { - super(build() - .defaultOperation(CREATE_WIN_EXE) - .bundler(CREATE_WIN_APP_IMAGE, WinBundlingEnvironment::createAppImage) - .bundler(CREATE_WIN_EXE, LazyLoad::sysEnv, WinBundlingEnvironment::createExePackage) - .bundler(CREATE_WIN_MSI, LazyLoad::sysEnv, WinBundlingEnvironment::createMsiPackage)); + super(build().mutate(builder -> { + var sysEnv = runOnce(WinSystemEnvironment::create); + + builder + .bundler(CREATE_WIN_EXE, sysEnv, WinBundlingEnvironment::createExePackage) + .bundler(CREATE_WIN_MSI, sysEnv, WinBundlingEnvironment::createMsiPackage); + }).defaultOperation(CREATE_WIN_EXE).bundler(CREATE_WIN_APP_IMAGE, WinBundlingEnvironment::createAppImage)); } private static void createMsiPackage(Options options, WinSystemEnvironment sysEnv) { createNativePackage(options, - WinFromOpions::createWinMsiPackage, + WinFromOptions.createWinMsiPackage(options), buildEnv()::create, WinPackagingPipeline.build(), (env, pkg, outputDir) -> { @@ -60,7 +61,7 @@ public class WinBundlingEnvironment extends DefaultBundlingEnvironment { private static void createExePackage(Options options, WinSystemEnvironment sysEnv) { createNativePackage(options, - WinFromOpions::createWinExePackage, + WinFromOptions.createWinExePackage(options), buildEnv()::create, WinPackagingPipeline.build(), (env, pkg, outputDir) -> { @@ -98,12 +99,4 @@ public class WinBundlingEnvironment extends DefaultBundlingEnvironment { } } - private static final class LazyLoad { - - static Result sysEnv() { - return SYS_ENV; - } - - private static final Result SYS_ENV = WinSystemEnvironment.create(); - } } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExePackager.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExePackager.java index 9a13a0f954d..21f941eedf5 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExePackager.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExePackager.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 @@ -52,7 +52,7 @@ final record WinExePackager(BuildEnv env, WinExePackage pkg, Path outputDir, Pat @Override public void accept(PackagingPipeline.Builder pipelineBuilder) { - pipelineBuilder.excludeDirFromCopying(outputDir) + pipelineBuilder .task(ExePackageTaskID.RUN_POST_MSI_USER_SCRIPT) .action(this::runPostMsiScript) .addDependency(PackageTaskID.CREATE_PACKAGE_FILE) @@ -79,8 +79,6 @@ final record WinExePackager(BuildEnv env, WinExePackage pkg, Path outputDir, Pat private void wrapMsiInExe() throws IOException { - Log.verbose(I18N.format("message.outputting-to-location", outputDir.toAbsolutePath())); - final var msi = msi(); // Copy template msi wrapper next to msi file @@ -102,7 +100,5 @@ final record WinExePackager(BuildEnv env, WinExePackage pkg, Path outputDir, Pat Files.copy(exePath, dstExePath, StandardCopyOption.REPLACE_EXISTING); dstExePath.toFile().setExecutable(true); - - Log.verbose(I18N.format("message.output-location", outputDir.toAbsolutePath())); } } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromOpions.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromOptions.java similarity index 99% rename from src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromOpions.java rename to src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromOptions.java index 6fd6d0a7b96..6009e2f6724 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromOpions.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromOptions.java @@ -49,7 +49,7 @@ import jdk.jpackage.internal.model.WinLauncher; import jdk.jpackage.internal.model.WinLauncherMixin; import jdk.jpackage.internal.model.WinMsiPackage; -final class WinFromOpions { +final class WinFromOptions { static WinApplication createWinApplication(Options options) { diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiPackager.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiPackager.java index 915d034bd82..c72b14a76d5 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiPackager.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiPackager.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 @@ -162,7 +162,7 @@ final class WinMsiPackager implements Consumer { @Override public void accept(PackagingPipeline.Builder pipelineBuilder) { - pipelineBuilder.excludeDirFromCopying(outputDir) + pipelineBuilder .task(PackagingPipeline.PackageTaskID.CREATE_CONFIG_FILES) .action(this::prepareConfigFiles) .add() @@ -315,7 +315,6 @@ final class WinMsiPackager implements Consumer { private void buildPackage() throws IOException { final var msiOut = outputDir.resolve(pkg.packageFileNameWithSuffix()); - Log.verbose(I18N.format("message.generating-msi", msiOut.toAbsolutePath())); wixPipeline.buildMsi(msiOut.toAbsolutePath()); } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixLauncherAsService.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixLauncherAsService.java index 833219eaa10..0755a920fd2 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixLauncherAsService.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixLauncherAsService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java index e2ed175fbf3..c4f8610312a 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.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 @@ -233,10 +233,10 @@ public enum WixTool { // Detect FIPS mode var fips = false; try { - final var exec = Executor.of(toolPath.toString(), "-?").setQuiet(true).saveOutput(true); - final var exitCode = exec.execute(); + final var result = Executor.of(toolPath.toString(), "-?").setQuiet(true).saveOutput(true).execute(); + final var exitCode = result.getExitCode(); if (exitCode != 0 /* 308 */) { - final var output = exec.getOutput(); + final var output = result.getOutput(); if (!output.isEmpty() && output.get(0).contains("error CNDL0308")) { fips = true; } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinApplicationMixin.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinApplicationMixin.java index 283b5d84ae3..fde29980599 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinApplicationMixin.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinApplicationMixin.java @@ -30,7 +30,15 @@ public interface WinApplicationMixin { record Stub(DottedVersion winVersion) implements WinApplicationMixin { public Stub(Application app) { - this(DottedVersion.greedy(app.version())); + this(parse(app.version())); + } + + private static DottedVersion parse(String strVersion) { + try { + return DottedVersion.greedy(strVersion); + } catch (IllegalArgumentException ex) { + throw new JPackageException(ex.getMessage(), ex); + } } } } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties index 1f485e6c6c8..38d0bd02bbb 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2017, 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 @@ -56,13 +56,9 @@ error.missing-service-installer.advice=Add 'service-installer.exe' service insta message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place. message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}". -message.outputting-to-location=Generating EXE for installer to: {0}. -message.output-location=Installer (.exe) saved to: {0} message.tool-version=Detected [{0}] version [{1}]. message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required. message.use-wix36-features=WiX {0} detected. Enabling advanced cleanup action. message.product-code=MSI ProductCode: {0}. message.upgrade-code=MSI UpgradeCode: {0}. message.preparing-msi-config=Preparing MSI config: {0}. -message.generating-msi=Generating MSI: {0}. - diff --git a/src/jdk.jpackage/windows/native/common/MsiUtils.h b/src/jdk.jpackage/windows/native/common/MsiUtils.h index 26fa9f91045..d501174268d 100644 --- a/src/jdk.jpackage/windows/native/common/MsiUtils.h +++ b/src/jdk.jpackage/windows/native/common/MsiUtils.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, 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/src/jdk.jpackage/windows/native/libjpackage/VersionInfo.cpp b/src/jdk.jpackage/windows/native/libjpackage/VersionInfo.cpp index 122757b3ac3..aa977be8a82 100644 --- a/src/jdk.jpackage/windows/native/libjpackage/VersionInfo.cpp +++ b/src/jdk.jpackage/windows/native/libjpackage/VersionInfo.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, 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/src/jdk.jpackage/windows/native/libmsica/Version.cpp b/src/jdk.jpackage/windows/native/libmsica/Version.cpp index 293f2dd36be..f8c570a1349 100644 --- a/src/jdk.jpackage/windows/native/libmsica/Version.cpp +++ b/src/jdk.jpackage/windows/native/libmsica/Version.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 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/src/jdk.jpackage/windows/native/libmsica/Version.h b/src/jdk.jpackage/windows/native/libmsica/Version.h index 4ef176d382f..6f91a36765e 100644 --- a/src/jdk.jpackage/windows/native/libmsica/Version.h +++ b/src/jdk.jpackage/windows/native/libmsica/Version.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 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/src/jdk.jpackage/windows/native/libmsica/libmsica.cpp b/src/jdk.jpackage/windows/native/libmsica/libmsica.cpp index edfea6dc136..f338c8ab103 100644 --- a/src/jdk.jpackage/windows/native/libmsica/libmsica.cpp +++ b/src/jdk.jpackage/windows/native/libmsica/libmsica.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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/jdk.jshell/share/classes/jdk/internal/jshell/tool/IOContext.java b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/IOContext.java index e6d43bb346e..d662b62c157 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/IOContext.java +++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/IOContext.java @@ -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/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/Startup.java b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/Startup.java index f2472ee5623..9925a0141f1 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/Startup.java +++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/Startup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties index 351a0f812ad..e9918a7f628 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties +++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2016, 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/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_de.properties b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_de.properties index cb93ae4ed57..744e71db667 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_de.properties +++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_de.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2016, 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/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_ja.properties b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_ja.properties index fa84c3f38fb..becf6854a74 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_ja.properties +++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_ja.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2016, 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/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_zh_CN.properties b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_zh_CN.properties index 879e9633e6a..c175db36da6 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_zh_CN.properties +++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_zh_CN.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2016, 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/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java index 099aba26968..7a1dd6c9877 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java +++ b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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/jdk.jshell/share/classes/jdk/jshell/OuterWrapMap.java b/src/jdk.jshell/share/classes/jdk/jshell/OuterWrapMap.java index 61fb3bdfc9a..ca1d7cc8fd3 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/OuterWrapMap.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/OuterWrapMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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/jdk.jshell/share/classes/jdk/jshell/SnippetMaps.java b/src/jdk.jshell/share/classes/jdk/jshell/SnippetMaps.java index 992d97a7040..dc82d1d06a1 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/SnippetMaps.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/SnippetMaps.java @@ -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/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java index d5b44b569a4..1cf0c85702f 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -43,7 +43,6 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Scope; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; -import static com.sun.source.tree.Tree.Kind.METHOD; import com.sun.source.tree.TypeParameterTree; import com.sun.source.tree.VariableTree; import com.sun.source.tree.YieldTree; @@ -81,6 +80,7 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.function.Predicate; +import java.util.TreeSet; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -114,7 +114,6 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.BiConsumer; @@ -149,14 +148,10 @@ import javax.tools.JavaFileManager.Location; import javax.tools.StandardLocation; import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo; -import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME; import static jdk.jshell.SourceCodeAnalysis.Completeness.DEFINITELY_INCOMPLETE; import static jdk.jshell.TreeDissector.printType; import static java.util.stream.Collectors.joining; -import static javax.lang.model.element.ElementKind.CONSTRUCTOR; -import static javax.lang.model.element.ElementKind.MODULE; -import static javax.lang.model.element.ElementKind.PACKAGE; import javax.lang.model.type.IntersectionType; import javax.lang.model.util.Elements; @@ -815,7 +810,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { }; String wrappedCode = codeWrap.wrapped(); return this.proc.taskFactory.analyze(codeWrap, task -> { - List result = new ArrayList<>(); + TreeSet result = new TreeSet<>(Comparator.comparing(Highlight::start).thenComparing(Highlight::end)); CompilationUnitTree cut = task.cuTrees().iterator().next(); Trees trees = task.trees(); SourcePositions sp = trees.getSourcePositions(); @@ -1050,8 +1045,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { } }.scan(cut, null); result.removeIf(h -> h.start() == h.end()); - Collections.sort(result, (h1, h2) -> h1.start() - h2.start()); - return result; + return new ArrayList<>(result); }); } diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java index 526580988ab..1df6dc5191b 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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/jdk.management/share/classes/jdk/management/HotSpotAOTCacheMXBean.java b/src/jdk.management/share/classes/jdk/management/HotSpotAOTCacheMXBean.java index 4a921c348d0..f8e1e5a7f05 100644 --- a/src/jdk.management/share/classes/jdk/management/HotSpotAOTCacheMXBean.java +++ b/src/jdk.management/share/classes/jdk/management/HotSpotAOTCacheMXBean.java @@ -39,7 +39,7 @@ import javax.management.ObjectName; *

    Direct access to the MXBean interface can be obtained with * {@link ManagementFactory#getPlatformMXBean(Class)}. * - * @since 27 + * @since 26 */ public interface HotSpotAOTCacheMXBean extends PlatformManagedObject { /** diff --git a/src/jdk.management/share/classes/jdk/management/VirtualThreadSchedulerMXBean.java b/src/jdk.management/share/classes/jdk/management/VirtualThreadSchedulerMXBean.java index 556c5184ffe..358ab23ca93 100644 --- a/src/jdk.management/share/classes/jdk/management/VirtualThreadSchedulerMXBean.java +++ b/src/jdk.management/share/classes/jdk/management/VirtualThreadSchedulerMXBean.java @@ -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 @@ -76,7 +76,7 @@ public interface VirtualThreadSchedulerMXBean extends PlatformManagedObject { * @throws IllegalArgumentException if size is less than the minimum, or * greater than the maximum, supported by the scheduler * @throws UnsupportedOperationException if changing the target - * parallelism is not suppored by the scheduler + * parallelism is not supported by the scheduler * * @see ForkJoinPool#setParallelism(int) */ diff --git a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixLoginModule.java b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixLoginModule.java index 785b178e296..4f6d8ca25ef 100644 --- a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixLoginModule.java +++ b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixLoginModule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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,7 +26,6 @@ package com.sun.security.auth.module; import java.util.*; -import java.io.IOException; import javax.security.auth.*; import javax.security.auth.callback.*; import javax.security.auth.login.*; @@ -34,6 +33,7 @@ import javax.security.auth.spi.*; import com.sun.security.auth.UnixPrincipal; import com.sun.security.auth.UnixNumericUserPrincipal; import com.sun.security.auth.UnixNumericGroupPrincipal; +import jdk.internal.util.OperatingSystem; /** * This {@code LoginModule} imports a user's Unix @@ -121,20 +121,34 @@ public class UnixLoginModule implements LoginModule { */ public boolean login() throws LoginException { - long[] unixGroups = null; + // Fail immediately on Windows to avoid cygwin-like functions + // being loaded, which are not supported. + if (OperatingSystem.isWindows()) { + throw new FailedLoginException + ("Failed in attempt to import " + + "the underlying system identity information" + + " on " + System.getProperty("os.name")); + } try { ss = new UnixSystem(); - } catch (UnsatisfiedLinkError ule) { + } catch (ExceptionInInitializerError | UnsatisfiedLinkError ule) { + // Errors could happen in either static blocks or the constructor, + // both have a cause. succeeded = false; - throw new FailedLoginException + var error = new FailedLoginException ("Failed in attempt to import " + "the underlying system identity information" + " on " + System.getProperty("os.name")); + if (ule.getCause() != null) { + error.initCause(ule.getCause()); + } + throw error; } userPrincipal = new UnixPrincipal(ss.getUsername()); UIDPrincipal = new UnixNumericUserPrincipal(ss.getUid()); GIDPrincipal = new UnixNumericGroupPrincipal(ss.getGid(), true); + long[] unixGroups = null; if (ss.getGroups() != null && ss.getGroups().length > 0) { unixGroups = ss.getGroups(); for (int i = 0; i < unixGroups.length; i++) { @@ -150,9 +164,11 @@ public class UnixLoginModule implements LoginModule { "succeeded importing info: "); System.out.println("\t\t\tuid = " + ss.getUid()); System.out.println("\t\t\tgid = " + ss.getGid()); - unixGroups = ss.getGroups(); - for (int i = 0; i < unixGroups.length; i++) { - System.out.println("\t\t\tsupp gid = " + unixGroups[i]); + System.out.println("\t\t\tusername = " + ss.getUsername()); + if (unixGroups != null) { + for (int i = 0; i < unixGroups.length; i++) { + System.out.println("\t\t\tsupp gid = " + unixGroups[i]); + } } } succeeded = true; diff --git a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixSystem.java b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixSystem.java index f3741c10404..c0e47fafc2c 100644 --- a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixSystem.java +++ b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/UnixSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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,38 +25,194 @@ package com.sun.security.auth.module; +import jdk.internal.util.Architecture; +import jdk.internal.util.OperatingSystem; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.Linker; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.StructLayout; +import java.lang.foreign.SymbolLookup; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; + +import static java.lang.foreign.MemoryLayout.PathElement.groupElement; + /** * This class implementation retrieves and makes available Unix * UID/GID/groups information for the current user. * * @since 1.4 */ +@SuppressWarnings("restricted") public class UnixSystem { - private native void getUnixInfo(); - - // Warning: the following 4 fields are used by Unix.c - - /** The current username. */ + /** + * The current username. + */ protected String username; - /** The current user ID. */ + /** + * The current user ID. + */ protected long uid; - /** The current group ID. */ + /** + * The current group ID. + */ protected long gid; - /** The current list of groups. */ + /** + * The current list of groups. + */ protected long[] groups; + private static final Linker LINKER = Linker.nativeLinker(); + private static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup() + .or(LINKER.defaultLookup()); + + private static final ValueLayout.OfByte C_CHAR + = (ValueLayout.OfByte) LINKER.canonicalLayouts().get("char"); + private static final ValueLayout.OfInt C_INT + = (ValueLayout.OfInt) LINKER.canonicalLayouts().get("int"); + private static final ValueLayout.OfLong C_LONG + = (ValueLayout.OfLong) LINKER.canonicalLayouts().get("long"); + private static final AddressLayout C_POINTER + = ((AddressLayout) LINKER.canonicalLayouts().get("void*")) + .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR)); + private static final ValueLayout C_SIZE_T + = (ValueLayout) LINKER.canonicalLayouts().get("size_t"); + + private static final StructLayout CAPTURE_STATE_LAYOUT + = Linker.Option.captureStateLayout(); + private static final VarHandle VH_errno = CAPTURE_STATE_LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("errno")); + + private static final MethodHandle MH_strerror + = LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow("strerror"), + FunctionDescriptor.of(C_POINTER, C_INT)); + + private static final MethodHandle MH_getgroups + = LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow("getgroups"), + FunctionDescriptor.of(C_INT, C_INT, C_POINTER), + Linker.Option.captureCallState("errno")); + private static final MethodHandle MH_getuid + = LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow("getuid"), + FunctionDescriptor.of(C_INT)); + + // Some architectures require appropriate zero or sign extension to 64 bit. + // Use long directly before https://bugs.openjdk.org/browse/JDK-8336664 is resolved. + private static final boolean calling_convention_requires_int_as_long + = Architecture.isPPC64() || Architecture.isPPC64LE() || Architecture.isS390(); + + // getpwuid_r does not work on AIX, instead we use another similar function + // extern int _posix_getpwuid_r(uid_t, struct passwd *, char *, int, struct passwd **) + private static final MethodHandle MH_getpwuid_r + = LINKER.downcallHandle(SYMBOL_LOOKUP.findOrThrow( + OperatingSystem.isAix() ? "_posix_getpwuid_r" : "getpwuid_r"), + FunctionDescriptor.of(C_INT, + calling_convention_requires_int_as_long ? C_LONG : C_INT, + C_POINTER, C_POINTER, + OperatingSystem.isAix() ? C_INT : C_SIZE_T, + C_POINTER)); + + private static final GroupLayout ML_passwd = MemoryLayout.structLayout( + C_POINTER.withName("pw_name"), + C_POINTER.withName("pw_passwd"), + C_INT.withName("pw_uid"), + C_INT.withName("pw_gid"), + // Different platforms have different fields in `struct passwd`. + // While we don't need those fields here, the struct needs to be + // big enough to avoid buffer overflow when `getpwuid_r` is called. + MemoryLayout.paddingLayout(100)); + + private static final VarHandle VH_pw_uid + = ML_passwd.varHandle(groupElement("pw_uid")); + private static final VarHandle VH_pw_gid + = ML_passwd.varHandle(groupElement("pw_gid")); + private static final VarHandle VH_pw_name + = ML_passwd.varHandle(groupElement("pw_name")); + + // The buffer size for the getpwuid_r function: + // 1. sysconf(_SC_GETPW_R_SIZE_MAX) on macOS is 4096 and 1024 on Linux, + // so we choose a bigger one. + // 2. We do not call sysconf() here because even _SC_GETPW_R_SIZE_MAX + // could be different on different platforms. + // 3. We choose int instead of long because the buffer_size argument + // might be `int` or `long` and converting from `long` to `int` + // requires an explicit cast. + private static final int GETPW_R_SIZE_MAX = 4096; + /** * Instantiate a {@code UnixSystem} and load * the native library to access the underlying system information. */ - @SuppressWarnings("restricted") public UnixSystem() { - System.loadLibrary("jaas"); - getUnixInfo(); + // The FFM code has only been tested on multiple platforms + // (including macOS, Linux, AIX, etc) and might fail on other + // *nix systems. Especially, the `passwd` struct could be defined + // differently. I've checked several and an extra 100 chars at the + // end seems enough. + try (Arena scope = Arena.ofConfined()) { + MemorySegment capturedState = scope.allocate(CAPTURE_STATE_LAYOUT); + int groupnum = (int) MH_getgroups.invokeExact(capturedState, 0, MemorySegment.NULL); + if (groupnum == -1) { + throw new RuntimeException("getgroups returns " + groupnum); + } + + var gs = scope.allocate(C_INT, groupnum); + groupnum = (int) MH_getgroups.invokeExact(capturedState, groupnum, gs); + if (groupnum == -1) { + var errno = (int) VH_errno.get(capturedState, 0L); + var errMsg = (MemorySegment) MH_strerror.invokeExact(errno); + throw new RuntimeException("getgroups returns " + groupnum + + ". Reason: " + errMsg.reinterpret(Long.MAX_VALUE).getString(0)); + } + + groups = new long[groupnum]; + for (int i = 0; i < groupnum; i++) { + groups[i] = Integer.toUnsignedLong(gs.getAtIndex(C_INT, i)); + } + + var pwd = scope.allocate(ML_passwd); + var result = scope.allocate(C_POINTER); + var buffer = scope.allocate(GETPW_R_SIZE_MAX); + + long tmpUid = Integer.toUnsignedLong((int) MH_getuid.invokeExact()); + + // Do not call invokeExact because the type of buffer_size is not + // always long in the underlying system. + int out; + if (calling_convention_requires_int_as_long) { + out = (int) MH_getpwuid_r.invoke( + tmpUid, pwd, buffer, GETPW_R_SIZE_MAX, result); + } else { + out = (int) MH_getpwuid_r.invoke( + (int) tmpUid, pwd, buffer, GETPW_R_SIZE_MAX, result); + } + if (out != 0) { + // If ERANGE (Result too large) is detected in a new platform, + // consider adjusting GETPW_R_SIZE_MAX. + var err = (MemorySegment) MH_strerror.invokeExact(out); + throw new RuntimeException(err.reinterpret(Long.MAX_VALUE).getString(0)); + } else if (result.get(ValueLayout.ADDRESS, 0).equals(MemorySegment.NULL)) { + throw new RuntimeException("the requested entry is not found"); + } else { + // uid_t and gid_t were defined unsigned. + uid = Integer.toUnsignedLong((int) VH_pw_uid.get(pwd, 0L)); + gid = Integer.toUnsignedLong((int) VH_pw_gid.get(pwd, 0L)); + username = ((MemorySegment) VH_pw_name.get(pwd, 0L)).getString(0); + } + } catch (Throwable t) { + var error = new UnsatisfiedLinkError("FFM calls failed"); + error.initCause(t); + throw error; + } } /** diff --git a/src/jdk.security.auth/unix/native/libjaas/Unix.c b/src/jdk.security.auth/unix/native/libjaas/Unix.c deleted file mode 100644 index 3a93df9ffc9..00000000000 --- a/src/jdk.security.auth/unix/native/libjaas/Unix.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2000, 2021, 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. - */ - -#include -#include "jni_util.h" -#include "com_sun_security_auth_module_UnixSystem.h" -#include -#include -#include -#include -#include - -#include - -/* - * Declare library specific JNI_Onload entry if static build - */ -DEF_STATIC_JNI_OnLoad - -JNIEXPORT void JNICALL -Java_com_sun_security_auth_module_UnixSystem_getUnixInfo - (JNIEnv *env, jobject obj) { - - int i; - char pwd_buf[1024]; - struct passwd *pwd = NULL; - struct passwd resbuf; - jfieldID userNameID; - jfieldID userID; - jfieldID groupID; - jfieldID supplementaryGroupID; - - jstring jstr; - jlongArray jgroups; - jlong *jgroupsAsArray; - jsize numSuppGroups; - gid_t *groups; - jclass cls; - - numSuppGroups = getgroups(0, NULL); - if (numSuppGroups == -1) { - return; - } - groups = (gid_t *)calloc(numSuppGroups, sizeof(gid_t)); - if (groups == NULL) { - jclass cls = (*env)->FindClass(env,"java/lang/OutOfMemoryError"); - if (cls != NULL) { - (*env)->ThrowNew(env, cls, NULL); - } - return; - } - - cls = (*env)->GetObjectClass(env, obj); - - supplementaryGroupID = (*env)->GetFieldID(env, cls, "groups", "[J"); - if (supplementaryGroupID == 0) { - goto cleanUpAndReturn; - } - - if (getgroups(numSuppGroups, groups) != -1) { - jgroups = (*env)->NewLongArray(env, numSuppGroups); - if (jgroups == NULL) { - goto cleanUpAndReturn; - } - jgroupsAsArray = (*env)->GetLongArrayElements(env, jgroups, 0); - if (jgroupsAsArray == NULL) { - goto cleanUpAndReturn; - } - for (i = 0; i < numSuppGroups; i++) { - jgroupsAsArray[i] = groups[i]; - } - (*env)->ReleaseLongArrayElements(env, jgroups, jgroupsAsArray, 0); - (*env)->SetObjectField(env, obj, supplementaryGroupID, jgroups); - } - - userNameID = (*env)->GetFieldID(env, cls, "username", "Ljava/lang/String;"); - if (userNameID == 0) { - goto cleanUpAndReturn; - } - - userID = (*env)->GetFieldID(env, cls, "uid", "J"); - if (userID == 0) { - goto cleanUpAndReturn; - } - - groupID = (*env)->GetFieldID(env, cls, "gid", "J"); - if (groupID == 0) { - goto cleanUpAndReturn; - } - - memset(pwd_buf, 0, sizeof(pwd_buf)); - if (getpwuid_r(getuid(), &resbuf, pwd_buf, sizeof(pwd_buf), &pwd) == 0 && - pwd != NULL) { - (*env)->SetLongField(env, obj, userID, pwd->pw_uid); - (*env)->SetLongField(env, obj, groupID, pwd->pw_gid); - jstr = (*env)->NewStringUTF(env, pwd->pw_name); - if (jstr == NULL) { - goto cleanUpAndReturn; - } - (*env)->SetObjectField(env, obj, userNameID, jstr); - } else { - (*env)->SetLongField(env, obj, userID, getuid()); - (*env)->SetLongField(env, obj, groupID, getgid()); - } -cleanUpAndReturn: - free(groups); - return; -} diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java index 7aec57ec2a0..ab334007ced 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/FreeInteractiveLayoutManager.java @@ -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 diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java index cb9487ae828..a73d7bb6dde 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/HierarchicalLayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java index 956d72924b9..9c285886e71 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutGraph.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java index 48599d78d75..3126a8dba9e 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutMover.java @@ -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 diff --git a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java index 2816115080e..2ef3211a3b0 100644 --- a/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java +++ b/src/utils/IdealGraphVisualizer/HierarchicalLayout/src/main/java/com/sun/hotspot/igv/hierarchicallayout/LayoutNode.java @@ -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/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java index 201fc51e15e..6af8bb851e2 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/EnableFreeLayoutAction.java @@ -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 diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java index 01ebb8ed9e3..0c9f636e373 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/LineWidget.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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/utils/LogCompilation/src/main/java/com/sun/hotspot/tools/compiler/LogParser.java b/src/utils/LogCompilation/src/main/java/com/sun/hotspot/tools/compiler/LogParser.java index 61cbc054200..9a806a85552 100644 --- a/src/utils/LogCompilation/src/main/java/com/sun/hotspot/tools/compiler/LogParser.java +++ b/src/utils/LogCompilation/src/main/java/com/sun/hotspot/tools/compiler/LogParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 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/utils/LogCompilation/src/main/java/com/sun/hotspot/tools/compiler/MakeNotEntrantEvent.java b/src/utils/LogCompilation/src/main/java/com/sun/hotspot/tools/compiler/MakeNotEntrantEvent.java index d230f1b4336..3a9a6c2cbef 100644 --- a/src/utils/LogCompilation/src/main/java/com/sun/hotspot/tools/compiler/MakeNotEntrantEvent.java +++ b/src/utils/LogCompilation/src/main/java/com/sun/hotspot/tools/compiler/MakeNotEntrantEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 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/test/benchmarks/micros-javac/src/main/java/org/openjdk/bench/langtools/javac/GroupJavacBenchmark.java b/test/benchmarks/micros-javac/src/main/java/org/openjdk/bench/langtools/javac/GroupJavacBenchmark.java index 5ca3f0bf648..9eb6c893cc6 100644 --- a/test/benchmarks/micros-javac/src/main/java/org/openjdk/bench/langtools/javac/GroupJavacBenchmark.java +++ b/test/benchmarks/micros-javac/src/main/java/org/openjdk/bench/langtools/javac/GroupJavacBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, 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/test/benchmarks/micros-javac/src/main/java/org/openjdk/bench/langtools/javac/JavacBenchmark.java b/test/benchmarks/micros-javac/src/main/java/org/openjdk/bench/langtools/javac/JavacBenchmark.java index 28c5c57a073..565ea4038c7 100644 --- a/test/benchmarks/micros-javac/src/main/java/org/openjdk/bench/langtools/javac/JavacBenchmark.java +++ b/test/benchmarks/micros-javac/src/main/java/org/openjdk/bench/langtools/javac/JavacBenchmark.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/test/benchmarks/micros-javac/src/main/java/org/openjdk/bench/langtools/javac/SingleJavacBenchmark.java b/test/benchmarks/micros-javac/src/main/java/org/openjdk/bench/langtools/javac/SingleJavacBenchmark.java index 2cc5c1a7c83..00c5bd7ee92 100644 --- a/test/benchmarks/micros-javac/src/main/java/org/openjdk/bench/langtools/javac/SingleJavacBenchmark.java +++ b/test/benchmarks/micros-javac/src/main/java/org/openjdk/bench/langtools/javac/SingleJavacBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, 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/test/docs/ProblemList.txt b/test/docs/ProblemList.txt index 4df8bbcc53c..c846678665d 100644 --- a/test/docs/ProblemList.txt +++ b/test/docs/ProblemList.txt @@ -39,7 +39,3 @@ # More than one label is allowed but must be on the same line. # ############################################################################# - -jdk/javadoc/doccheck/checks/jdkCheckLinks.java 8370249 generic-all -jdk/javadoc/doccheck/checks/jdkCheckHtml.java 8370970 generic-all -jdk/javadoc/doccheck/checks/jdkDoctypeBadcharsCheck.java 8370970 generic-all diff --git a/test/failure_handler/src/share/classes/jdk/test/failurehandler/HtmlPage.java b/test/failure_handler/src/share/classes/jdk/test/failurehandler/HtmlPage.java index d8fd13fdd7c..af28135e673 100644 --- a/test/failure_handler/src/share/classes/jdk/test/failurehandler/HtmlPage.java +++ b/test/failure_handler/src/share/classes/jdk/test/failurehandler/HtmlPage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 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 @@ -23,19 +23,50 @@ package jdk.test.failurehandler; +import java.io.FileWriter; +import java.io.IOException; import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Objects; public class HtmlPage implements AutoCloseable { + static final String STYLE_SHEET_FILENAME = "failure-handler-style.css"; + static final String SCRIPT_FILENAME = "failure-handler-script.js"; + private final PrintWriter writer; private final HtmlSection rootSection; - public HtmlPage(PrintWriter writer) { - Objects.requireNonNull(writer, "writer cannot be null"); - this.writer = writer; + /** + * Constructs a {@code HtmlPage} + * + * @param dir The directory into which the HTML file and related resources will be created + * @param htmlFileName The HTML file name + * @param append if {@code true} then the content will be appended to the file represented + * by the {@code htmlFileName}, else the {@code htmlFileName} will be overwritten + * with the new content + * @throws IllegalArgumentException if {@code dir} is not a directory or if the + * {@code htmlFileName} is {@linkplain String#isBlank() blank} + * @throws IOException if there is an error constructing file resource(s) for this HTML page + */ + public HtmlPage(final Path dir, final String htmlFileName, final boolean append) + throws IOException { + Objects.requireNonNull(dir, "directory cannot be null"); + Objects.requireNonNull(htmlFileName, "HTML file name cannot be null"); + if (!Files.isDirectory(dir)) { + throw new IllegalArgumentException(dir + " is not a directory"); + } + if (htmlFileName.isBlank()) { + throw new IllegalArgumentException("HTML file name cannot be blank"); + } + final FileWriter fileWriter = new FileWriter(dir.resolve(htmlFileName).toFile(), append); + this.writer = new PrintWriter(fileWriter, true); + createScriptFile(dir); + createStyleSheetFile(dir); rootSection = new HtmlSection(writer); } + @Override public void close() { writer.close(); @@ -44,4 +75,71 @@ public class HtmlPage implements AutoCloseable { public HtmlSection getRootSection() { return rootSection; } + + private static void createStyleSheetFile(final Path destDir) throws IOException { + final Path styleSheet = destDir.resolve(STYLE_SHEET_FILENAME); + if (Files.exists(styleSheet)) { + return; + } + final String content = """ + div { display:none;} + """; + Files.writeString(styleSheet, content); + } + + private static void createScriptFile(final Path destDir) throws IOException { + final Path script = destDir.resolve(SCRIPT_FILENAME); + if (Files.exists(script)) { + return; + } + final String content = """ + function doShow(e) { + while (e != null) { + if (e.tagName == 'DIV') { + e.style.display = 'block'; + } + e = e.parentNode; + } + } + + function showHandler(event) { + elementId = this.dataset.show; + elementToShow = document.getElementById(elementId); + doShow(elementToShow); + } + + function toggleHandler(event) { + toggleElementId = this.dataset.toggle; + elementToToggle = document.getElementById(toggleElementId); + d = elementToToggle.style.display; + if (d == 'block') { + elementToToggle.style.display = 'none'; + } else { + doShow(elementToToggle); + } + } + + function bodyLoadHandler() { + const index = location.href.indexOf("#"); + if (index != -1) { + doShow(document.getElementById(location.href.substring(index + 1))); + } + // elements that require the "toggleHandler" function to be registered + // as an event handler for the onclick event + const requiringToggleHandler = document.querySelectorAll("[data-toggle]"); + for (const e of requiringToggleHandler) { + e.addEventListener("click", toggleHandler); + } + // elements that require the "showHandler" function to be registered + // as an event handler for the onclick event + const requiringShowHandler = document.querySelectorAll("[data-show]"); + for (const e of requiringShowHandler) { + e.addEventListener("click", showHandler); + } + } + // register a onload event handler + window.addEventListener("DOMContentLoaded", bodyLoadHandler); + """; + Files.writeString(script, content); + } } diff --git a/test/failure_handler/src/share/classes/jdk/test/failurehandler/HtmlSection.java b/test/failure_handler/src/share/classes/jdk/test/failurehandler/HtmlSection.java index 141caf450bd..53ffab95a1e 100644 --- a/test/failure_handler/src/share/classes/jdk/test/failurehandler/HtmlSection.java +++ b/test/failure_handler/src/share/classes/jdk/test/failurehandler/HtmlSection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, 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 @@ -57,41 +57,15 @@ public class HtmlSection { if (rootSection == null) { this.rootSection = this; this.pw.println(""); - this.pw.println("\n" - + "\n" - + "\n" - + ""); - this.pw.println(""); + this.pw.println(""); + this.pw.println( + ""); + this.pw.println( + ""); + this.pw.println(""); + + this.pw.println(""); } else { this.rootSection = rootSection; this.pw.print("