Merge remote-tracking branch 'jdk/master' into fix-old-reference-processing

This commit is contained in:
William Kemper 2026-01-12 15:37:40 -08:00
commit 88101211f1
154 changed files with 10110 additions and 2268 deletions

View File

@ -458,12 +458,10 @@ char* os::map_memory_to_file(char* base, size_t size, int fd) {
warning("Failed mmap to file. (%s)", os::strerror(errno));
return nullptr;
}
if (base != nullptr && addr != base) {
if (!os::release_memory(addr, size)) {
warning("Could not release memory on unsuccessful file mapping");
}
return nullptr;
}
// The requested address should be the same as the returned address when using MAP_FIXED
// as per POSIX.
assert(base == nullptr || addr == base, "base should equal addr when using MAP_FIXED");
return addr;
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2021 SAP SE. All rights reserved.
* Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026 SAP SE. 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
@ -946,7 +946,7 @@ static int create_sharedmem_file(const char* dirname, const char* filename, size
if (result == -1 ) break;
if (!os::write(fd, &zero_int, 1)) {
if (errno == ENOSPC) {
warning("Insufficient space for shared memory file:\n %s\nTry using the -Djava.io.tmpdir= option to select an alternate temp location.\n", filename);
warning("Insufficient space for shared memory file: %s/%s\n", dirname, filename);
}
result = OS_ERR;
break;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2013 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -26,8 +26,7 @@
#ifndef OS_CPU_AIX_PPC_PREFETCH_AIX_PPC_INLINE_HPP
#define OS_CPU_AIX_PPC_PREFETCH_AIX_PPC_INLINE_HPP
#include "runtime/prefetch.hpp"
// Included in runtime/prefetch.inline.hpp
inline void Prefetch::read(const void *loc, intx interval) {
#if !defined(USE_XLC_BUILTINS)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, Red Hat Inc. All rights reserved.
* Copyright (c) 2021, Azul Systems, Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
@ -27,8 +27,7 @@
#ifndef OS_CPU_BSD_AARCH64_PREFETCH_BSD_AARCH64_INLINE_HPP
#define OS_CPU_BSD_AARCH64_PREFETCH_BSD_AARCH64_INLINE_HPP
#include "runtime/prefetch.hpp"
// Included in runtime/prefetch.inline.hpp
inline void Prefetch::read (const void *loc, intx interval) {
if (interval >= 0)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 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,8 +25,7 @@
#ifndef OS_CPU_BSD_X86_PREFETCH_BSD_X86_INLINE_HPP
#define OS_CPU_BSD_X86_PREFETCH_BSD_X86_INLINE_HPP
#include "runtime/prefetch.hpp"
// Included in runtime/prefetch.inline.hpp
inline void Prefetch::read (const void *loc, intx interval) {
__asm__ ("prefetcht0 (%0,%1,1)" : : "r" (loc), "r" (interval));

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright 2007, 2008 Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -26,7 +26,7 @@
#ifndef OS_CPU_BSD_ZERO_PREFETCH_BSD_ZERO_INLINE_HPP
#define OS_CPU_BSD_ZERO_PREFETCH_BSD_ZERO_INLINE_HPP
#include "runtime/prefetch.hpp"
// Included in runtime/prefetch.inline.hpp
inline void Prefetch::read(const void* loc, intx interval) {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, Red Hat Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -26,8 +26,7 @@
#ifndef OS_CPU_LINUX_AARCH64_PREFETCH_LINUX_AARCH64_INLINE_HPP
#define OS_CPU_LINUX_AARCH64_PREFETCH_LINUX_AARCH64_INLINE_HPP
#include "runtime/prefetch.hpp"
// Included in runtime/prefetch.inline.hpp
inline void Prefetch::read (const void *loc, intx interval) {
if (interval >= 0)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 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,7 +25,7 @@
#ifndef OS_CPU_LINUX_ARM_PREFETCH_LINUX_ARM_INLINE_HPP
#define OS_CPU_LINUX_ARM_PREFETCH_LINUX_ARM_INLINE_HPP
#include "runtime/prefetch.hpp"
// Included in runtime/prefetch.inline.hpp
inline void Prefetch::read (const void *loc, intx interval) {
#if defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_5TE__)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2013 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -26,8 +26,7 @@
#ifndef OS_CPU_LINUX_PPC_PREFETCH_LINUX_PPC_INLINE_HPP
#define OS_CPU_LINUX_PPC_PREFETCH_LINUX_PPC_INLINE_HPP
#include "runtime/prefetch.hpp"
// Included in runtime/prefetch.inline.hpp
inline void Prefetch::read(const void *loc, intx interval) {
__asm__ __volatile__ (

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2021, Huawei Technologies Co., Ltd. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -26,7 +26,7 @@
#ifndef OS_CPU_LINUX_RISCV_VM_PREFETCH_LINUX_RISCV_INLINE_HPP
#define OS_CPU_LINUX_RISCV_VM_PREFETCH_LINUX_RISCV_INLINE_HPP
#include "runtime/prefetch.hpp"
// Included in runtime/prefetch.inline.hpp
inline void Prefetch::read (const void *loc, intx interval) {
if (interval >= 0 && UseZicbop) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -26,7 +26,7 @@
#ifndef OS_CPU_LINUX_S390_PREFETCH_LINUX_S390_INLINE_HPP
#define OS_CPU_LINUX_S390_PREFETCH_LINUX_S390_INLINE_HPP
#include "runtime/prefetch.hpp"
// Included in runtime/prefetch.inline.hpp
inline void Prefetch::read(const void* loc, intx interval) {
// No prefetch instructions on z/Architecture -> implement trivially.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 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,8 +25,7 @@
#ifndef OS_CPU_LINUX_X86_PREFETCH_LINUX_X86_INLINE_HPP
#define OS_CPU_LINUX_X86_PREFETCH_LINUX_X86_INLINE_HPP
#include "runtime/prefetch.hpp"
// Included in runtime/prefetch.inline.hpp
inline void Prefetch::read (const void *loc, intx interval) {
__asm__ ("prefetcht0 (%0,%1,1)" : : "r" (loc), "r" (interval));

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright 2007, 2008 Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -26,7 +26,7 @@
#ifndef OS_CPU_LINUX_ZERO_PREFETCH_LINUX_ZERO_INLINE_HPP
#define OS_CPU_LINUX_ZERO_PREFETCH_LINUX_ZERO_INLINE_HPP
#include "runtime/prefetch.hpp"
// Included in runtime/prefetch.inline.hpp
inline void Prefetch::read(const void* loc, intx interval) {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, Microsoft Corporation. All rights reserved.
* Copyright (c) 2020, 2026, Microsoft Corporation. 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,8 +25,7 @@
#ifndef OS_CPU_WINDOWS_AARCH64_PREFETCH_WINDOWS_AARCH64_INLINE_HPP
#define OS_CPU_WINDOWS_AARCH64_PREFETCH_WINDOWS_AARCH64_INLINE_HPP
#include "runtime/prefetch.hpp"
// Included in runtime/prefetch.inline.hpp
inline void Prefetch::read (const void *loc, intx interval) {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 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,7 +25,7 @@
#ifndef OS_CPU_WINDOWS_X86_PREFETCH_WINDOWS_X86_INLINE_HPP
#define OS_CPU_WINDOWS_X86_PREFETCH_WINDOWS_X86_INLINE_HPP
#include "runtime/prefetch.hpp"
// Included in runtime/prefetch.inline.hpp
inline void Prefetch::read (const void *loc, intx interval) {}
inline void Prefetch::write(void *loc, intx interval) {}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 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
@ -686,7 +686,8 @@ HeapWord* G1CollectedHeap::attempt_allocation_humongous(size_t word_size) {
// the check before we do the actual allocation. The reason for doing it
// before the allocation is that we avoid having to keep track of the newly
// allocated memory while we do a GC.
if (policy()->need_to_start_conc_mark("concurrent humongous allocation",
// Only try that if we can actually perform a GC.
if (is_init_completed() && policy()->need_to_start_conc_mark("concurrent humongous allocation",
word_size)) {
try_collect(word_size, GCCause::_g1_humongous_allocation, collection_counters(this));
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -46,7 +46,7 @@
#include "oops/access.inline.hpp"
#include "oops/compressedOops.inline.hpp"
#include "oops/oop.inline.hpp"
#include "runtime/prefetch.hpp"
#include "runtime/prefetch.inline.hpp"
#include "runtime/threads.hpp"
#include "runtime/threadSMR.hpp"
#include "utilities/bitMap.inline.hpp"

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 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,6 +28,7 @@
#include "gc/serial/serialHeap.inline.hpp"
#include "gc/shared/space.hpp"
#include "memory/iterator.inline.hpp"
#include "runtime/prefetch.inline.hpp"
#include "utilities/align.hpp"
void CardTableRS::scan_old_to_young_refs(TenuredGeneration* tg, HeapWord* saved_top) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 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
@ -34,7 +34,6 @@
#include "memory/virtualspace.hpp"
#include "runtime/mutex.hpp"
#include "runtime/perfData.hpp"
#include "runtime/prefetch.inline.hpp"
// A Generation models a heap area for similarly-aged objects.
// It will contain one ore more spaces holding the actual objects.

View File

@ -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
@ -70,6 +70,7 @@
#include "runtime/init.hpp"
#include "runtime/java.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/prefetch.inline.hpp"
#include "runtime/threads.hpp"
#include "runtime/vmThread.hpp"
#include "services/memoryManager.hpp"

View File

@ -700,7 +700,7 @@ void OopStorage::Block::release_entries(uintx releasing, OopStorage* owner) {
// then someone else has made such a claim and the deferred update has not
// yet been processed and will include our change, so we don't need to do
// anything further.
if (_deferred_updates_next.compare_exchange(nullptr, this) == nullptr) {
if (_deferred_updates_next.compare_set(nullptr, this)) {
// Successfully claimed. Push, with self-loop for end-of-list.
Block* head = owner->_deferred_updates.load_relaxed();
while (true) {

View File

@ -56,7 +56,7 @@ void PretouchTask::work(uint worker_id) {
char* cur_end = cur_start + MIN2(_chunk_size, pointer_delta(_end_addr, cur_start, 1));
if (cur_start >= cur_end) {
break;
} else if (cur_start == _cur_addr.compare_exchange(cur_start, cur_end)) {
} else if (_cur_addr.compare_set(cur_start, cur_end)) {
os::pretouch_memory(cur_start, cur_end, _page_size);
} // Else attempt to claim chunk failed, so try again.
}

View File

@ -183,8 +183,8 @@ protected:
_age.store_relaxed(new_age);
}
Age cmpxchg_age(Age old_age, Age new_age) {
return _age.compare_exchange(old_age, new_age);
bool par_set_age(Age old_age, Age new_age) {
return _age.compare_set(old_age, new_age);
}
idx_t age_top_relaxed() const {
@ -345,7 +345,7 @@ protected:
using TaskQueueSuper<N, MT>::age_relaxed;
using TaskQueueSuper<N, MT>::set_age_relaxed;
using TaskQueueSuper<N, MT>::cmpxchg_age;
using TaskQueueSuper<N, MT>::par_set_age;
using TaskQueueSuper<N, MT>::age_top_relaxed;
using TaskQueueSuper<N, MT>::increment_index;

View File

@ -170,8 +170,7 @@ bool GenericTaskQueue<E, MT, N>::pop_local_slow(uint localBot, Age oldAge) {
if (localBot == oldAge.top()) {
// No competing pop_global has yet incremented "top"; we'll try to
// install new_age, thus claiming the element.
Age tempAge = cmpxchg_age(oldAge, newAge);
if (tempAge == oldAge) {
if (par_set_age(oldAge, newAge)) {
// We win.
assert_not_underflow(localBot, age_top_relaxed());
TASKQUEUE_STATS_ONLY(stats.record_pop_slow());
@ -283,12 +282,12 @@ typename GenericTaskQueue<E, MT, N>::PopResult GenericTaskQueue<E, MT, N>::pop_g
idx_t new_top = increment_index(oldAge.top());
idx_t new_tag = oldAge.tag() + ((new_top == 0) ? 1 : 0);
Age newAge(new_top, new_tag);
Age resAge = cmpxchg_age(oldAge, newAge);
bool result = par_set_age(oldAge, newAge);
// Note that using "bottom" here might fail, since a pop_local might
// have decremented it.
assert_not_underflow(localBot, newAge.top());
return resAge == oldAge ? PopResult::Success : PopResult::Contended;
return result ? PopResult::Success : PopResult::Contended;
}
inline int randomParkAndMiller(int *seed0) {

View File

@ -61,7 +61,12 @@ ShenandoahGenerationalControlThread::ShenandoahGenerationalControlThread() :
void ShenandoahGenerationalControlThread::run_service() {
// This is the only instance of request. It is important that request.generation
// does not change between a concurrent cycle failure and the start of a degenerated
// cycle. We initialize it with the young generation to handle the pathological case
// where the very first cycle is degenerated (some tests exercise this path).
ShenandoahGCRequest request;
request.generation = _heap->young_generation();
while (!should_terminate()) {
// Figure out if we have pending requests.
@ -77,12 +82,10 @@ void ShenandoahGenerationalControlThread::run_service() {
// If the cycle was cancelled, continue the next iteration to deal with it. Otherwise,
// if there was no other cycle requested, cleanup and wait for the next request.
if (!_heap->cancelled_gc()) {
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
if (_requested_gc_cause == GCCause::_no_gc) {
set_gc_mode(ml, none);
ml.wait();
}
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
if (_requested_gc_cause == GCCause::_no_gc) {
set_gc_mode(ml, none);
ml.wait();
}
}
@ -96,8 +99,7 @@ void ShenandoahGenerationalControlThread::stop_service() {
log_debug(gc, thread)("Stopping control thread");
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
_heap->cancel_gc(GCCause::_shenandoah_stop_vm);
_requested_gc_cause = GCCause::_shenandoah_stop_vm;
notify_cancellation(ml, GCCause::_shenandoah_stop_vm);
notify_control_thread(ml, GCCause::_shenandoah_stop_vm);
// We can't wait here because it may interfere with the active cycle's ability
// to reach a safepoint (this runs on a java thread).
}
@ -105,29 +107,39 @@ void ShenandoahGenerationalControlThread::stop_service() {
void ShenandoahGenerationalControlThread::check_for_request(ShenandoahGCRequest& request) {
// Hold the lock while we read request cause and generation
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
if (_heap->cancelled_gc()) {
// The previous request was cancelled. Either it was cancelled for an allocation
// failure (degenerated cycle), or old marking was cancelled to run a young collection.
// In either case, the correct generation for the next cycle can be determined by
// the cancellation cause.
request.cause = _heap->clear_cancellation(GCCause::_shenandoah_concurrent_gc);
if (request.cause == GCCause::_shenandoah_concurrent_gc) {
log_debug(gc, thread)("cancelled cause: %s, requested cause: %s",
GCCause::to_string(_heap->cancelled_cause()), GCCause::to_string(_requested_gc_cause));
request.cause = _requested_gc_cause;
if (ShenandoahCollectorPolicy::is_allocation_failure(request.cause)) {
if (_degen_point == ShenandoahGC::_degenerated_unset) {
request.generation = _heap->young_generation();
_degen_point = ShenandoahGC::_degenerated_outside_cycle;
} else {
assert(request.generation != nullptr, "Must know which generation to use for degenerated cycle");
}
} else {
request.cause = _requested_gc_cause;
if (request.cause == GCCause::_shenandoah_concurrent_gc) {
// This is a regulator request. It is also possible that the regulator "canceled" an old mark,
// so we can clear that here. This clear operation will only clear the cancellation if it is
// a regulator request.
_heap->clear_cancellation(GCCause::_shenandoah_concurrent_gc);
}
request.generation = _requested_generation;
// Only clear these if we made a request from them. In the case of a cancelled gc,
// we do not want to inadvertently lose this pending request.
_requested_gc_cause = GCCause::_no_gc;
_requested_generation = nullptr;
}
log_debug(gc, thread)("request.cause: %s, request.generation: %s",
GCCause::to_string(request.cause), request.generation == nullptr ? "None" : request.generation->name());
_requested_gc_cause = GCCause::_no_gc;
_requested_generation = nullptr;
if (request.cause == GCCause::_no_gc || request.cause == GCCause::_shenandoah_stop_vm) {
return;
}
assert(request.generation != nullptr, "request.generation cannot be null, cause is: %s", GCCause::to_string(request.cause));
GCMode mode;
if (ShenandoahCollectorPolicy::is_allocation_failure(request.cause)) {
mode = prepare_for_allocation_failure_gc(request);
@ -140,11 +152,9 @@ void ShenandoahGenerationalControlThread::check_for_request(ShenandoahGCRequest&
}
ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_allocation_failure_gc(ShenandoahGCRequest &request) {
if (_degen_point == ShenandoahGC::_degenerated_unset) {
_degen_point = ShenandoahGC::_degenerated_outside_cycle;
request.generation = _heap->young_generation();
} else if (request.generation->is_old()) {
// Important: not all paths update the request.generation. This is intentional.
// A degenerated cycle must use the same generation carried over from the previous request.
if (request.generation->is_old()) {
// This means we degenerated during the young bootstrap for the old generation
// cycle. The following degenerated cycle should therefore also be young.
request.generation = _heap->young_generation();
@ -583,6 +593,8 @@ bool ShenandoahGenerationalControlThread::check_cancellation_or_degen(Shenandoah
if (ShenandoahCollectorPolicy::is_allocation_failure(_heap->cancelled_cause())) {
assert(_degen_point == ShenandoahGC::_degenerated_unset,
"Should not be set yet: %s", ShenandoahGC::degen_point_to_string(_degen_point));
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
_requested_gc_cause = _heap->cancelled_cause();
_degen_point = point;
log_debug(gc, thread)("Cancellation detected:, reason: %s, degen point: %s",
GCCause::to_string(_heap->cancelled_cause()),
@ -628,9 +640,7 @@ void ShenandoahGenerationalControlThread::service_stw_degenerated_cycle(const Sh
void ShenandoahGenerationalControlThread::request_gc(GCCause::Cause cause) {
if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) {
// GC should already be cancelled. Here we are just notifying the control thread to
// wake up and handle the cancellation request, so we don't need to set _requested_gc_cause.
notify_cancellation(cause);
notify_control_thread(cause);
} else if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) {
handle_requested_gc(cause);
}
@ -648,7 +658,8 @@ bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGenera
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
if (gc_mode() == servicing_old) {
if (!preempt_old_marking(generation)) {
log_debug(gc, thread)("Cannot start young, old collection is not preemptible");
// Global should be able to cause old collection to be abandoned
log_debug(gc, thread)("Cannot start %s, old collection is not preemptible", generation->name());
return false;
}
@ -656,7 +667,7 @@ bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGenera
log_info(gc)("Preempting old generation mark to allow %s GC", generation->name());
while (gc_mode() == servicing_old) {
ShenandoahHeap::heap()->cancel_gc(GCCause::_shenandoah_concurrent_gc);
notify_cancellation(ml, GCCause::_shenandoah_concurrent_gc);
notify_control_thread(ml, GCCause::_shenandoah_concurrent_gc, generation);
ml.wait();
}
return true;
@ -690,21 +701,34 @@ void ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause c
void ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation) {
assert(_control_lock.is_locked(), "Request lock must be held here");
log_debug(gc, thread)("Notify control (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(cause), generation->name());
_requested_gc_cause = cause;
_requested_generation = generation;
ml.notify();
if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) {
// We have already observed a request to handle an allocation failure. We cannot allow
// another request (System.gc or regulator) to subvert the degenerated cycle.
log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause));
} else {
log_debug(gc, thread)("Notify control (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(cause), generation->name());
_requested_gc_cause = cause;
_requested_generation = generation;
ml.notify();
}
}
void ShenandoahGenerationalControlThread::notify_cancellation(GCCause::Cause cause) {
void ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause cause) {
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
notify_cancellation(ml, cause);
notify_control_thread(ml, cause);
}
void ShenandoahGenerationalControlThread::notify_cancellation(MonitorLocker& ml, GCCause::Cause cause) {
assert(_heap->cancelled_gc(), "GC should already be cancelled");
log_debug(gc,thread)("Notify control (%s): %s", gc_mode_name(gc_mode()), GCCause::to_string(cause));
ml.notify();
void ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause) {
assert(_control_lock.is_locked(), "Request lock must be held here");
if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) {
// We have already observed a request to handle an allocation failure. We cannot allow
// another request (System.gc or regulator) to subvert the degenerated cycle.
log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause));
} else {
log_debug(gc, thread)("Notify control (%s): %s", gc_mode_name(gc_mode()), GCCause::to_string(cause));
_requested_gc_cause = cause;
ml.notify();
}
}
bool ShenandoahGenerationalControlThread::preempt_old_marking(ShenandoahGeneration* generation) {

View File

@ -138,16 +138,13 @@ private:
// Return printable name for the given gc mode.
static const char* gc_mode_name(GCMode mode);
// Takes the request lock and updates the requested cause and generation, then notifies the control thread.
// The overloaded variant should be used when the _control_lock is already held.
// These notify the control thread after updating _requested_gc_cause and (optionally) _requested_generation.
// Updating the requested generation is not necessary for allocation failures nor when stopping the thread.
void notify_control_thread(GCCause::Cause cause);
void notify_control_thread(MonitorLocker& ml, GCCause::Cause cause);
void notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation);
void notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation);
// Notifies the control thread, but does not update the requested cause or generation.
// The overloaded variant should be used when the _control_lock is already held.
void notify_cancellation(GCCause::Cause cause);
void notify_cancellation(MonitorLocker& ml, GCCause::Cause cause);
// Configure the heap to age objects and regions if the aging period has elapsed.
void maybe_set_aging_cycle();

View File

@ -46,13 +46,13 @@ public:
bool start_old_collection();
static ShenandoahGenerationalHeap* heap() {
assert(ShenandoahCardBarrier, "Should have card barrier to use genenrational heap");
assert(ShenandoahCardBarrier, "Should have card barrier to use generational heap");
CollectedHeap* heap = Universe::heap();
return cast(heap);
}
static ShenandoahGenerationalHeap* cast(CollectedHeap* heap) {
assert(ShenandoahCardBarrier, "Should have card barrier to use genenrational heap");
assert(ShenandoahCardBarrier, "Should have card barrier to use generational heap");
return checked_cast<ShenandoahGenerationalHeap*>(heap);
}

View File

@ -149,6 +149,13 @@ bool ShenandoahRegulatorThread::start_global_cycle() const {
bool ShenandoahRegulatorThread::request_concurrent_gc(ShenandoahGeneration* generation) const {
double now = os::elapsedTime();
// This call may find the control thread waiting on workers which have suspended
// to allow a safepoint to run. If this regulator thread does not yield, the safepoint
// will not run. The worker threads won't progress, the control thread won't progress,
// and the regulator thread may never yield. Therefore, we leave the suspendible
// thread set before making this call.
SuspendibleThreadSetLeaver leaver;
bool accepted = _control_thread->request_concurrent_gc(generation);
if (LogTarget(Debug, gc, thread)::is_enabled() && accepted) {
double wait_time = os::elapsedTime() - now;

View File

@ -1112,8 +1112,6 @@ Node *ModINode::Ideal(PhaseGVN *phase, bool can_reshape) {
if( !ti->is_con() ) return nullptr;
jint con = ti->get_con();
Node *hook = new Node(1);
// First, special check for modulo 2^k-1
if( con >= 0 && con < max_jint && is_power_of_2(con+1) ) {
uint k = exact_log2(con+1); // Extract k
@ -1129,7 +1127,9 @@ Node *ModINode::Ideal(PhaseGVN *phase, bool can_reshape) {
Node *x = in(1); // Value being mod'd
Node *divisor = in(2); // Also is mask
hook->init_req(0, x); // Add a use to x to prevent him from dying
// Add a use to x to prevent it from dying
Node* hook = new Node(1);
hook->init_req(0, x);
// Generate code to reduce X rapidly to nearly 2^k-1.
for( int i = 0; i < trip_count; i++ ) {
Node *xl = phase->transform( new AndINode(x,divisor) );
@ -1185,6 +1185,7 @@ Node *ModINode::Ideal(PhaseGVN *phase, bool can_reshape) {
}
// Save in(1) so that it cannot be changed or deleted
Node* hook = new Node(1);
hook->init_req(0, in(1));
// Divide using the transform from DivI to MulL
@ -1407,8 +1408,6 @@ Node *ModLNode::Ideal(PhaseGVN *phase, bool can_reshape) {
if( !tl->is_con() ) return nullptr;
jlong con = tl->get_con();
Node *hook = new Node(1);
// Expand mod
if(con >= 0 && con < max_jlong && is_power_of_2(con + 1)) {
uint k = log2i_exact(con + 1); // Extract k
@ -1426,13 +1425,15 @@ Node *ModLNode::Ideal(PhaseGVN *phase, bool can_reshape) {
Node *x = in(1); // Value being mod'd
Node *divisor = in(2); // Also is mask
hook->init_req(0, x); // Add a use to x to prevent him from dying
// Add a use to x to prevent it from dying
Node* hook = new Node(1);
hook->init_req(0, x);
// Generate code to reduce X rapidly to nearly 2^k-1.
for( int i = 0; i < trip_count; i++ ) {
Node *xl = phase->transform( new AndLNode(x,divisor) );
Node *xh = phase->transform( new RShiftLNode(x,phase->intcon(k)) ); // Must be signed
x = phase->transform( new AddLNode(xh,xl) );
hook->set_req(0, x); // Add a use to x to prevent him from dying
hook->set_req(0, x); // Add a use to x to prevent it from dying
}
// Generate sign-fixup code. Was original value positive?
@ -1482,6 +1483,8 @@ Node *ModLNode::Ideal(PhaseGVN *phase, bool can_reshape) {
}
// Save in(1) so that it cannot be changed or deleted
// Add a use to x to prevent him from dying
Node* hook = new Node(1);
hook->init_req(0, in(1));
// Divide using the transform from DivL to MulL

View File

@ -75,6 +75,7 @@
// v.release_store(x) -> void
// v.release_store_fence(x) -> void
// v.compare_exchange(x, y [, o]) -> T
// v.compare_set(x, y [, o]) -> bool
// v.exchange(x [, o]) -> T
//
// (2) All atomic types are default constructible.
@ -267,6 +268,11 @@ public:
return AtomicAccess::cmpxchg(value_ptr(), compare_value, new_value, order);
}
bool compare_set(T compare_value, T new_value,
atomic_memory_order order = memory_order_conservative) {
return compare_exchange(compare_value, new_value, order) == compare_value;
}
T exchange(T new_value,
atomic_memory_order order = memory_order_conservative) {
return AtomicAccess::xchg(this->value_ptr(), new_value, order);
@ -479,6 +485,13 @@ public:
order));
}
bool compare_set(T compare_value, T new_value,
atomic_memory_order order = memory_order_conservative) {
return _value.compare_set(decay(compare_value),
decay(new_value),
order);
}
T exchange(T new_value, atomic_memory_order order = memory_order_conservative) {
return recover(_value.exchange(decay(new_value), order));
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2019, 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
@ -25,9 +25,24 @@
#ifndef SHARE_RUNTIME_PREFETCH_INLINE_HPP
#define SHARE_RUNTIME_PREFETCH_INLINE_HPP
#include "runtime/prefetch.hpp"
#include "memory/allStatic.hpp"
#include "utilities/macros.hpp"
// If calls to prefetch methods are in a loop, the loop should be cloned
// such that if Prefetch{Scan,Copy}Interval and/or PrefetchFieldInterval
// say not to do prefetching, these methods aren't called. At the very
// least, they take up a memory issue slot. They should be implemented
// as inline assembly code: doing an actual call isn't worth the cost.
class Prefetch : AllStatic {
public:
// Prefetch anticipating read; must not fault, semantically a no-op
static void read(const void* loc, intx interval);
// Prefetch anticipating write; must not fault, semantically a no-op
static void write(void* loc, intx interval);
};
#include OS_CPU_HEADER_INLINE(prefetch)
#endif // SHARE_RUNTIME_PREFETCH_INLINE_HPP

View File

@ -157,7 +157,7 @@ inline bool ConcurrentHashTable<CONFIG, MT>::
if (is_locked()) {
return false;
}
if (_first.compare_exchange(expect, node) == expect) {
if (_first.compare_set(expect, node)) {
return true;
}
return false;
@ -172,7 +172,7 @@ inline bool ConcurrentHashTable<CONFIG, MT>::
}
// We will expect a clean first pointer.
Node* tmp = first();
if (_first.compare_exchange(tmp, set_state(tmp, STATE_LOCK_BIT)) == tmp) {
if (_first.compare_set(tmp, set_state(tmp, STATE_LOCK_BIT))) {
return true;
}
return false;

View File

@ -181,7 +181,7 @@ void GenericWaitBarrier::Cell::disarm(int32_t expected_tag) {
tag, waiters);
int64_t new_state = encode(0, waiters);
if (_state.compare_exchange(state, new_state) == state) {
if (_state.compare_set(state, new_state)) {
// Successfully disarmed.
break;
}
@ -218,7 +218,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) {
tag, waiters);
int64_t new_state = encode(tag, waiters + 1);
if (_state.compare_exchange(state, new_state) == state) {
if (_state.compare_set(state, new_state)) {
// Success! Proceed to wait.
break;
}
@ -247,7 +247,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) {
tag, waiters);
int64_t new_state = encode(tag, waiters - 1);
if (_state.compare_exchange(state, new_state) == state) {
if (_state.compare_set(state, new_state)) {
// Success!
break;
}

View File

@ -2045,19 +2045,26 @@ public final class String
return encode(Charset.defaultCharset(), coder(), value);
}
boolean bytesCompatible(Charset charset) {
boolean bytesCompatible(Charset charset, int srcIndex, int numChars) {
if (isLatin1()) {
if (charset == ISO_8859_1.INSTANCE) {
return true; // ok, same encoding
} else if (charset == UTF_8.INSTANCE || charset == US_ASCII.INSTANCE) {
return !StringCoding.hasNegatives(value, 0, value.length); // ok, if ASCII-compatible
return !StringCoding.hasNegatives(value, srcIndex, numChars); // ok, if ASCII-compatible
}
}
return false;
}
void copyToSegmentRaw(MemorySegment segment, long offset) {
MemorySegment.copy(value, 0, segment, ValueLayout.JAVA_BYTE, offset, value.length);
void copyToSegmentRaw(MemorySegment segment, long offset, int srcIndex, int srcLength) {
if (!isLatin1()) {
// This method is intended to be used together with bytesCompatible, which currently only supports
// latin1 strings. In the future, bytesCompatible could be updated to handle more cases, like
// UTF-16 strings (when the platform and charset endianness match, and the String doesnt contain
// unpaired surrogates). If that happens, copyToSegmentRaw should also be updated.
throw new IllegalStateException("This string does not support copyToSegmentRaw");
}
MemorySegment.copy(value, srcIndex, segment, ValueLayout.JAVA_BYTE, offset, srcLength);
}
/**

View File

@ -2331,13 +2331,13 @@ public final class System {
}
@Override
public void copyToSegmentRaw(String string, MemorySegment segment, long offset) {
string.copyToSegmentRaw(segment, offset);
public void copyToSegmentRaw(String string, MemorySegment segment, long offset, int srcIndex, int srcLength) {
string.copyToSegmentRaw(segment, offset, srcIndex, srcLength);
}
@Override
public boolean bytesCompatible(String string, Charset charset) {
return string.bytesCompatible(charset);
public boolean bytesCompatible(String string, Charset charset, int srcIndex, int numChars) {
return string.bytesCompatible(charset, srcIndex, numChars);
}
});
}

View File

@ -31,6 +31,8 @@ import java.lang.classfile.Instruction;
import java.lang.classfile.Opcode;
import jdk.internal.classfile.impl.AbstractInstruction;
import jdk.internal.classfile.impl.BytecodeHelpers;
import jdk.internal.classfile.impl.Util;
/**
* Models a local variable increment instruction in the {@code code} array of a
@ -82,6 +84,37 @@ public sealed interface IncrementInstruction extends Instruction
* @throws IllegalArgumentException if {@code slot} or {@code constant} is out of range
*/
static IncrementInstruction of(int slot, int constant) {
return new AbstractInstruction.UnboundIncrementInstruction(slot, constant);
var opcode = BytecodeHelpers.validateAndIsWideIinc(slot, constant) ? Opcode.IINC_W: Opcode.IINC;
return new AbstractInstruction.UnboundIncrementInstruction(opcode, slot, constant);
}
/**
* {@return an increment instruction}
* <p>
* {@code slot} must be {@link java.lang.classfile##u1 u1} and
* {@code constant} must be within {@code [-128, 127]} for
* {@link Opcode#IINC iinc}, or {@code slot} must be
* {@link java.lang.classfile##u2 u2} and {@code constant} must be
* within {@code [-32768, 32767]} for {@link Opcode#IINC_W wide iinc}.
*
* @apiNote
* The explicit {@code op} argument allows creating {@code wide} or
* regular increment instructions when {@code slot} and
* {@code constant} can be encoded with more optimized
* increment instructions.
*
* @param op the opcode for the specific type of increment instruction,
* which must be of kind {@link Opcode.Kind#INCREMENT}
* @param slot the local variable slot to increment
* @param constant the increment constant
* @throws IllegalArgumentException if the opcode kind is not
* {@link Opcode.Kind#INCREMENT} or {@code slot} or
* {@code constant} is out of range
* @since 27
*/
static IncrementInstruction of(Opcode op, int slot, int constant) {
Util.checkKind(op, Opcode.Kind.INCREMENT);
BytecodeHelpers.validateIncrement(op, slot, constant);
return new AbstractInstruction.UnboundIncrementInstruction(op, slot, constant);
}
}

View File

@ -1296,12 +1296,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
* over the decoding process is required.
* <p>
* Getting a string from a segment with a known byte offset and
* known byte length can be done like so:
* {@snippet lang=java :
* byte[] bytes = new byte[length];
* MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, length);
* return new String(bytes, charset);
* }
* known byte length can be done using {@link #getString(long, Charset, long)}.
*
* @param offset offset in bytes (relative to this segment address) at which this
* access operation will occur
@ -1328,6 +1323,40 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
*/
String getString(long offset, Charset charset);
/**
* Reads a string from this segment at the given offset, using the provided length
* and charset.
* <p>
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement string. The {@link
* java.nio.charset.CharsetDecoder} class should be used when more control
* over the decoding process is required.
* <p>
* If the string contains any {@code '\0'} characters, they will be read as well.
* This differs from {@link #getString(long, Charset)}, which will only read up
* to the first {@code '\0'}, resulting in truncation for string data that contains
* the {@code '\0'} character.
*
* @param offset offset in bytes (relative to this segment address) at which this
* access operation will occur
* @param charset the charset used to {@linkplain Charset#newDecoder() decode} the
* string bytes
* @param byteLength length, in bytes, of the region of memory to read and decode into
* a string
* @return a Java string constructed from the bytes read from the given starting
* address up to the given length
* @throws IllegalArgumentException if the size of the string is greater than the
* largest string supported by the platform
* @throws IndexOutOfBoundsException if {@code offset < 0}
* @throws IndexOutOfBoundsException if {@code offset > byteSize() - byteLength}
* @throws IllegalStateException if the {@linkplain #scope() scope} associated with
* this segment is not {@linkplain Scope#isAlive() alive}
* @throws WrongThreadException if this method is called from a thread {@code T},
* such that {@code isAccessibleBy(T) == false}
* @throws IllegalArgumentException if {@code byteLength < 0}
*/
String getString(long offset, Charset charset, long byteLength);
/**
* Writes the given string into this segment at the given offset, converting it to
* a null-terminated byte sequence using the {@linkplain StandardCharsets#UTF_8 UTF-8}
@ -1366,7 +1395,8 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
* If the given string contains any {@code '\0'} characters, they will be
* copied as well. This means that, depending on the method used to read
* the string, such as {@link MemorySegment#getString(long)}, the string
* will appear truncated when read again.
* will appear truncated when read again. The string can be read without
* truncation using {@link #getString(long, Charset, long)}.
*
* @param offset offset in bytes (relative to this segment address) at which this
* access operation will occur, the final address of this write
@ -2606,6 +2636,50 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
elementCount);
}
/**
* Copies the byte sequence of the given string encoded using the provided charset
* to the destination segment.
* <p>
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement string. The {@link
* java.nio.charset.CharsetDecoder} class should be used when more control
* over the decoding process is required.
* <p>
* If the given string contains any {@code '\0'} characters, they will be
* copied as well. This means that, depending on the method used to read
* the string, such as {@link MemorySegment#getString(long)}, the string
* will appear truncated when read again. The string can be read without
* truncation using {@link #getString(long, Charset, long)}.
*
* @param src the Java string to be written into the destination segment
* @param dstEncoding the charset used to {@linkplain Charset#newEncoder() encode}
* the string bytes.
* @param srcIndex the starting character index of the source string
* @param dst the destination segment
* @param dstOffset the starting offset, in bytes, of the destination segment
* @param numChars the number of characters to be copied
* @throws IllegalStateException if the {@linkplain #scope() scope} associated with
* {@code dst} is not {@linkplain Scope#isAlive() alive}
* @throws WrongThreadException if this method is called from a thread {@code T},
* such that {@code dst.isAccessibleBy(T) == false}
* @throws IndexOutOfBoundsException if either {@code srcIndex}, {@code numChars}, or {@code dstOffset}
* are {@code < 0}
* @throws IndexOutOfBoundsException if {@code srcIndex > src.length() - numChars}
* @throws IllegalArgumentException if {@code dst} is {@linkplain #isReadOnly() read-only}
* @throws IndexOutOfBoundsException if {@code dstOffset > dstSegment.byteSize() - B} where {@code B} is the size,
* in bytes, of the substring of {@code src} encoded using the given charset
* @return the number of copied bytes.
*/
@ForceInline
static long copy(String src, Charset dstEncoding, int srcIndex, MemorySegment dst, long dstOffset, int numChars) {
Objects.requireNonNull(src);
Objects.requireNonNull(dstEncoding);
Objects.requireNonNull(dst);
Objects.checkFromIndexSize(srcIndex, numChars, src.length());
return AbstractMemorySegmentImpl.copy(src, dstEncoding, srcIndex, dst, dstOffset, numChars);
}
/**
* Finds and returns the relative offset, in bytes, of the first mismatch between the
* source and the destination segments. More specifically, the bytes at offset

View File

@ -111,7 +111,8 @@ public interface SegmentAllocator {
* If the given string contains any {@code '\0'} characters, they will be
* copied as well. This means that, depending on the method used to read
* the string, such as {@link MemorySegment#getString(long)}, the string
* will appear truncated when read again.
* will appear truncated when read again. The string can be read without
* truncation using {@link MemorySegment#getString(long, Charset, long)}.
*
* @param str the Java string to be converted into a C string
* @param charset the charset used to {@linkplain Charset#newEncoder() encode} the
@ -137,10 +138,10 @@ public interface SegmentAllocator {
int termCharSize = StringSupport.CharsetKind.of(charset).terminatorCharSize();
MemorySegment segment;
int length;
if (StringSupport.bytesCompatible(str, charset)) {
if (StringSupport.bytesCompatible(str, charset, 0, str.length())) {
length = str.length();
segment = allocateNoInit((long) length + termCharSize);
StringSupport.copyToSegmentRaw(str, segment, 0);
StringSupport.copyToSegmentRaw(str, segment, 0, 0, str.length());
} else {
byte[] bytes = str.getBytes(charset);
length = bytes.length;
@ -153,6 +154,53 @@ public interface SegmentAllocator {
return segment;
}
/**
* Encodes a Java string using the provided charset and stores the resulting
* byte array into a memory segment.
* <p>
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement byte array. The
* {@link java.nio.charset.CharsetEncoder} class should be used when more
* control over the encoding process is required.
* <p>
* If the given string contains any {@code '\0'} characters, they will be
* copied as well. This means that, depending on the method used to read
* the string, such as {@link MemorySegment#getString(long)}, the string
* will appear truncated when read again. The string can be read without
* truncation using {@link MemorySegment#getString(long, Charset, long)}.
*
* @param str the Java string to be encoded
* @param charset the charset used to {@linkplain Charset#newEncoder() encode} the
* string bytes
* @param srcIndex the starting index of the source string
* @param numChars the number of characters to be copied
* @return a new native segment containing the encoded string
* @throws IndexOutOfBoundsException if either {@code srcIndex} or {@code numChars} are {@code < 0}
* @throws IndexOutOfBoundsException if {@code srcIndex > str.length() - numChars}
*
* @implSpec The default implementation for this method copies the contents of the
* provided Java string into a new memory segment obtained by calling
* {@code this.allocate(B)}, where {@code B} is the size, in bytes, of
* the string encoded using the provided charset
* (e.g. {@code str.getBytes(charset).length});
*/
@ForceInline
default MemorySegment allocateFrom(String str, Charset charset, int srcIndex, int numChars) {
Objects.requireNonNull(charset);
Objects.requireNonNull(str);
Objects.checkFromIndexSize(srcIndex, numChars, str.length());
MemorySegment segment;
if (StringSupport.bytesCompatible(str, charset, srcIndex, numChars)) {
segment = allocateNoInit(numChars);
StringSupport.copyToSegmentRaw(str, segment, 0, srcIndex, numChars);
} else {
byte[] bytes = str.substring(srcIndex, srcIndex + numChars).getBytes(charset);
segment = allocateNoInit(bytes.length);
MemorySegment.copy(bytes, 0, segment, ValueLayout.JAVA_BYTE, 0, bytes.length);
}
return segment;
}
/**
* {@return a new memory segment initialized with the provided byte value}
* <p>

View File

@ -634,10 +634,10 @@ public interface JavaLangAccess {
/**
* Copy the string bytes to an existing segment, avoiding intermediate copies.
*/
void copyToSegmentRaw(String string, MemorySegment segment, long offset);
void copyToSegmentRaw(String string, MemorySegment segment, long offset, int srcIndex, int srcLength);
/**
* Are the string bytes compatible with the given charset?
*/
boolean bytesCompatible(String string, Charset charset);
boolean bytesCompatible(String string, Charset charset, int srcIndex, int numChars);
}

View File

@ -832,10 +832,8 @@ public abstract sealed class AbstractInstruction
final int slot;
final int constant;
public UnboundIncrementInstruction(int slot, int constant) {
super(BytecodeHelpers.validateAndIsWideIinc(slot, constant)
? Opcode.IINC_W
: Opcode.IINC);
public UnboundIncrementInstruction(Opcode op, int slot, int constant) {
super(op);
this.slot = slot;
this.constant = constant;
}

View File

@ -450,6 +450,13 @@ public class BytecodeHelpers {
return ret;
}
public static void validateIncrement(Opcode opcode, int slot, int constant) {
if (validateAndIsWideIinc(slot, constant) && opcode != Opcode.IINC_W) {
throw new IllegalArgumentException(
"IINC: operands require wide encoding for %s".formatted(opcode));
}
}
public static void validateRet(Opcode opcode, int slot) {
if (opcode == Opcode.RET && (slot & ~0xFF) == 0 ||
opcode == Opcode.RET_W && (slot & ~0xFFFF) == 0)

View File

@ -551,6 +551,13 @@ public abstract sealed class AbstractMemorySegmentImpl
unsafeGetOffset() == that.unsafeGetOffset();
}
@Override
public String getString(long offset, Charset charset, long byteLength) {
Utils.checkNonNegativeArgument(byteLength, "byteLength");
Objects.requireNonNull(charset);
return StringSupport.read(this, offset, charset, byteLength);
}
@Override
public int hashCode() {
return Objects.hash(
@ -702,6 +709,16 @@ public abstract sealed class AbstractMemorySegmentImpl
}
}
@ForceInline
public static long copy(String src, Charset dstEncoding, int srcIndex, MemorySegment dst, long dstOffset, int numChars) {
Objects.requireNonNull(src);
Objects.requireNonNull(dstEncoding);
Objects.requireNonNull(dst);
AbstractMemorySegmentImpl destImpl = (AbstractMemorySegmentImpl)dst;
return StringSupport.copyBytes(src, destImpl, dstEncoding, dstOffset, srcIndex, numChars);
}
// accessors
@ForceInline

View File

@ -30,11 +30,14 @@ import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.ScopedMemoryAccess;
import jdk.internal.util.Architecture;
import jdk.internal.util.ArraysSupport;
import jdk.internal.util.Preconditions;
import jdk.internal.vm.annotation.ForceInline;
import java.lang.foreign.MemorySegment;
import java.lang.reflect.Array;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.Objects;
import static java.lang.foreign.ValueLayout.*;
@ -58,6 +61,27 @@ public final class StringSupport {
};
}
@ForceInline
public static String read(AbstractMemorySegmentImpl segment, long offset, Charset charset, long length) {
return readBytes(segment, offset, charset, length);
}
@ForceInline
public static String readBytes(AbstractMemorySegmentImpl segment, long offset, Charset charset, long length) {
if (length > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Required length exceeds implementation limit");
}
final int lengthBytes = (int) length;
final byte[] bytes = new byte[lengthBytes];
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, lengthBytes);
try {
return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset);
} catch (CharacterCodingException _) {
// use replacement characters for malformed input
return new String(bytes, charset);
}
}
@ForceInline
public static void write(AbstractMemorySegmentImpl segment, long offset, Charset charset, String string) {
switch (CharsetKind.of(charset)) {
@ -70,14 +94,7 @@ public final class StringSupport {
@ForceInline
private static String readByte(AbstractMemorySegmentImpl segment, long offset, Charset charset) {
final int len = strlenByte(segment, offset, segment.byteSize());
final byte[] bytes = new byte[len];
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, len);
try {
return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset);
} catch (CharacterCodingException _) {
// use replacement characters for malformed input
return new String(bytes, charset);
}
return readBytes(segment, offset, charset, len);
}
@ForceInline
@ -89,14 +106,7 @@ public final class StringSupport {
@ForceInline
private static String readShort(AbstractMemorySegmentImpl segment, long offset, Charset charset) {
int len = strlenShort(segment, offset, segment.byteSize());
byte[] bytes = new byte[len];
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, len);
try {
return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset);
} catch (CharacterCodingException _) {
// use replacement characters for malformed input
return new String(bytes, charset);
}
return readBytes(segment, offset, charset, len);
}
@ForceInline
@ -108,14 +118,7 @@ public final class StringSupport {
@ForceInline
private static String readInt(AbstractMemorySegmentImpl segment, long offset, Charset charset) {
int len = strlenInt(segment, offset, segment.byteSize());
byte[] bytes = new byte[len];
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, len);
try {
return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset);
} catch (CharacterCodingException _) {
// use replacement characters for malformed input
return new String(bytes, charset);
}
return readBytes(segment, offset, charset, len);
}
@ForceInline
@ -345,22 +348,26 @@ public final class StringSupport {
}
}
public static boolean bytesCompatible(String string, Charset charset) {
return JAVA_LANG_ACCESS.bytesCompatible(string, charset);
public static boolean bytesCompatible(String string, Charset charset, int srcIndex, int numChars) {
return JAVA_LANG_ACCESS.bytesCompatible(string, charset, srcIndex, numChars);
}
public static int copyBytes(String string, MemorySegment segment, Charset charset, long offset) {
if (bytesCompatible(string, charset)) {
copyToSegmentRaw(string, segment, offset);
return string.length();
return copyBytes(string, segment, charset, offset, 0, string.length());
}
public static int copyBytes(String string, MemorySegment segment, Charset charset, long offset, int srcIndex, int numChars) {
if (bytesCompatible(string, charset, srcIndex, numChars)) {
copyToSegmentRaw(string, segment, offset, srcIndex, numChars);
return numChars;
} else {
byte[] bytes = string.getBytes(charset);
byte[] bytes = string.substring(srcIndex, srcIndex + numChars).getBytes(charset);
MemorySegment.copy(bytes, 0, segment, JAVA_BYTE, offset, bytes.length);
return bytes.length;
}
}
public static void copyToSegmentRaw(String string, MemorySegment segment, long offset) {
JAVA_LANG_ACCESS.copyToSegmentRaw(string, segment, offset);
public static void copyToSegmentRaw(String string, MemorySegment segment, long offset, int srcIndex, int srcLength) {
JAVA_LANG_ACCESS.copyToSegmentRaw(string, segment, offset, srcIndex, srcLength);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 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
@ -68,7 +68,7 @@ public class ECDSAOperations {
public ECDSAOperations(ECOperations ecOps, ECPoint basePoint) {
this.ecOps = ecOps;
this.basePoint = toAffinePoint(basePoint, ecOps.getField());
this.basePoint = AffinePoint.fromECPoint(basePoint, ecOps.getField());
}
public ECOperations getEcOperations() {
@ -79,14 +79,6 @@ public class ECDSAOperations {
return ecOps.multiply(basePoint, scalar).asAffine();
}
public static AffinePoint toAffinePoint(ECPoint point,
IntegerFieldModuloP field) {
ImmutableIntegerModuloP affineX = field.getElement(point.getAffineX());
ImmutableIntegerModuloP affineY = field.getElement(point.getAffineY());
return new AffinePoint(affineX, affineY);
}
public static
Optional<ECDSAOperations> forParameters(ECParameterSpec ecParams) {
Optional<ECOperations> curveOps =

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 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
@ -120,6 +120,15 @@ public interface TypeMirror extends AnnotatedConstruct {
* The results of {@code t1.equals(t2)} and
* {@code Types.isSameType(t1, t2)} may differ.
*
* @apiNote The identity of a {@code TypeMirror} involves implicit
* state not directly accessible from its methods, including state
* about the presence of unrelated types. {@code TypeMirror}
* objects created by different implementations of these
* interfaces should <i>not</i> be expected to compare as equal
* even if &quot;the same&quot; type is being modeled; this is
* analogous to the inequality of {@code Class} objects for the
* same class file loaded through different class loaders.
*
* @param obj the object to be compared with this type
* @return {@code true} if the specified object is equal to this one
*/

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 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
@ -106,6 +106,15 @@ public interface Types {
* {@code TypeMirror} objects can have different annotations and
* still be considered the same.
*
* @apiNote The identity of a {@code TypeMirror} involves implicit
* state not directly accessible from its methods, including state
* about the presence of unrelated types. {@code TypeMirror}
* objects created by different implementations of these
* interfaces should <i>not</i> be expected to compare as equal
* even if &quot;the same&quot; type is being modeled; this is
* analogous to the inequality of {@code Class} objects for the
* same class file loaded through different class loaders.
*
* @param t1 the first type
* @param t2 the second type
* @return {@code true} if and only if the two types are the same

View File

@ -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
@ -50,6 +50,7 @@ import jdk.jpackage.internal.model.LinuxLauncher;
import jdk.jpackage.internal.model.LinuxPackage;
import jdk.jpackage.internal.model.Package;
import jdk.jpackage.internal.util.CompositeProxy;
import jdk.jpackage.internal.util.Enquoter;
import jdk.jpackage.internal.util.PathUtils;
import jdk.jpackage.internal.util.XmlUtils;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2021, 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
@ -27,13 +27,12 @@ package jdk.jpackage.internal;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Collection;
import java.util.Objects;
import java.util.Collections;
import java.util.Set;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -48,9 +47,6 @@ public final class LibProvidersLookup {
return (new ToolValidator(TOOL_LDD).validate() == null);
}
public LibProvidersLookup() {
}
LibProvidersLookup setPackageLookup(PackageLookup v) {
packageLookup = v;
return this;
@ -87,23 +83,20 @@ public final class LibProvidersLookup {
}
private static List<Path> getNeededLibsForFile(Path path) throws IOException {
List<Path> result = new ArrayList<>();
int ret = Executor.of(TOOL_LDD, path.toString()).setOutputConsumer(lines -> {
lines.map(line -> {
Matcher matcher = LIB_IN_LDD_OUTPUT_REGEX.matcher(line);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}).filter(Objects::nonNull).map(Path::of).forEach(result::add);
}).execute();
final var result = Executor.of(TOOL_LDD, path.toString()).saveOutput().execute();
if (ret != 0) {
if (result.getExitCode() != 0) {
// objdump failed. This is OK if the tool was applied to not a binary file
return Collections.emptyList();
}
return result;
return result.stdout().stream().map(line -> {
Matcher matcher = LIB_IN_LDD_OUTPUT_REGEX.matcher(line);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}).filter(Objects::nonNull).map(Path::of).toList();
}
private static Collection<Path> getNeededLibsForFiles(List<Path> paths) {

View File

@ -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
@ -33,6 +33,7 @@ import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_LINUX_R
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
import jdk.jpackage.internal.cli.Options;
import jdk.jpackage.internal.cli.StandardBundlingOperation;
@ -44,19 +45,34 @@ import jdk.jpackage.internal.util.Result;
public class LinuxBundlingEnvironment extends DefaultBundlingEnvironment {
public LinuxBundlingEnvironment() {
super(build()
.defaultOperation(() -> {
return LazyLoad.SYS_ENV.value().map(LinuxSystemEnvironment::nativePackageType).map(DESCRIPTORS::get);
})
.bundler(CREATE_LINUX_APP_IMAGE, LinuxBundlingEnvironment::createAppImage)
.bundler(CREATE_LINUX_DEB, LazyLoad::debSysEnv, LinuxBundlingEnvironment::createDebPackage)
.bundler(CREATE_LINUX_RPM, LazyLoad::rpmSysEnv, LinuxBundlingEnvironment::createRpmPackage));
super(build().mutate(builder -> {
// Wrap the generic Linux system environment supplier in the run-once wrapper
// as this supplier is called from both RPM and DEB Linux system environment suppliers.
var sysEnv = runOnce(() -> {
return LinuxSystemEnvironment.create();
});
Supplier<Result<LinuxDebSystemEnvironment>> debSysEnv = () -> {
return LinuxDebSystemEnvironment.create(sysEnv.get());
};
Supplier<Result<LinuxRpmSystemEnvironment>> rpmSysEnv = () -> {
return LinuxRpmSystemEnvironment.create(sysEnv.get());
};
builder.defaultOperation(() -> {
return sysEnv.get().value().map(LinuxSystemEnvironment::nativePackageType).map(DESCRIPTORS::get);
})
.bundler(CREATE_LINUX_DEB, debSysEnv, LinuxBundlingEnvironment::createDebPackage)
.bundler(CREATE_LINUX_RPM, rpmSysEnv, LinuxBundlingEnvironment::createRpmPackage);
}).bundler(CREATE_LINUX_APP_IMAGE, LinuxBundlingEnvironment::createAppImage));
}
private static void createDebPackage(Options options, LinuxDebSystemEnvironment sysEnv) {
createNativePackage(options,
LinuxFromOptions.createLinuxDebPackage(options),
LinuxFromOptions.createLinuxDebPackage(options, sysEnv),
buildEnv()::create,
LinuxBundlingEnvironment::buildPipeline,
(env, pkg, outputDir) -> {
@ -67,7 +83,7 @@ public class LinuxBundlingEnvironment extends DefaultBundlingEnvironment {
private static void createRpmPackage(Options options, LinuxRpmSystemEnvironment sysEnv) {
createNativePackage(options,
LinuxFromOptions.createLinuxRpmPackage(options),
LinuxFromOptions.createLinuxRpmPackage(options, sysEnv),
buildEnv()::create,
LinuxBundlingEnvironment::buildPipeline,
(env, pkg, outputDir) -> {
@ -90,23 +106,6 @@ public class LinuxBundlingEnvironment extends DefaultBundlingEnvironment {
return new BuildEnvFromOptions().predefinedAppImageLayout(APPLICATION_LAYOUT);
}
private static final class LazyLoad {
static Result<LinuxDebSystemEnvironment> debSysEnv() {
return DEB_SYS_ENV;
}
static Result<LinuxRpmSystemEnvironment> rpmSysEnv() {
return RPM_SYS_ENV;
}
private static final Result<LinuxSystemEnvironment> SYS_ENV = LinuxSystemEnvironment.create();
private static final Result<LinuxDebSystemEnvironment> DEB_SYS_ENV = LinuxDebSystemEnvironment.create(SYS_ENV);
private static final Result<LinuxRpmSystemEnvironment> RPM_SYS_ENV = LinuxRpmSystemEnvironment.create(SYS_ENV);
}
private static final Map<PackageType, BundlingOperationDescriptor> DESCRIPTORS = Stream.of(
CREATE_LINUX_DEB,
CREATE_LINUX_RPM

View File

@ -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
@ -25,7 +25,6 @@
package jdk.jpackage.internal;
import static jdk.jpackage.internal.model.StandardPackageType.LINUX_DEB;
import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction;
import java.io.IOException;
@ -76,11 +75,11 @@ final class LinuxDebPackager extends LinuxPackager<LinuxDebPackage> {
try {
// Try the real path first as it works better on newer Ubuntu versions
return findProvidingPackages(realPath, sysEnv.dpkg());
return findProvidingPackages(realPath, sysEnv);
} catch (IOException ex) {
// Try the default path if differ
if (!realPath.equals(file)) {
return findProvidingPackages(file, sysEnv.dpkg());
return findProvidingPackages(file, sysEnv);
} else {
throw ex;
}
@ -107,7 +106,7 @@ final class LinuxDebPackager extends LinuxPackager<LinuxDebPackage> {
properties.forEach(property -> cmdline.add(property.name));
Map<String, String> actualValues = Executor.of(cmdline.toArray(String[]::new))
Map<String, String> actualValues = Executor.of(cmdline)
.saveOutput(true)
.executeExpectSuccess()
.getOutput().stream()
@ -158,9 +157,8 @@ final class LinuxDebPackager extends LinuxPackager<LinuxDebPackage> {
cmdline.addAll(List.of("-b", env.appImageDir().toString(), debFile.toAbsolutePath().toString()));
// run dpkg
RetryExecutor.retryOnKnownErrorMessage(
"semop(1): encountered an error: Invalid argument").execute(
cmdline.toArray(String[]::new));
Executor.of(cmdline).retryOnKnownErrorMessage(
"semop(1): encountered an error: Invalid argument").execute();
Log.verbose(I18N.format("message.output-to-location", debFile.toAbsolutePath()));
}
@ -233,7 +231,7 @@ final class LinuxDebPackager extends LinuxPackager<LinuxDebPackage> {
}
}
private static Stream<String> findProvidingPackages(Path file, Path dpkg) throws IOException {
private static Stream<String> findProvidingPackages(Path file, LinuxDebSystemEnvironment sysEnv) throws IOException {
//
// `dpkg -S` command does glob pattern lookup. If not the absolute path
// to the file is specified it might return mltiple package names.
@ -279,9 +277,9 @@ final class LinuxDebPackager extends LinuxPackager<LinuxDebPackage> {
Set<String> archPackages = new HashSet<>();
Set<String> otherPackages = new HashSet<>();
var debArch = LinuxPackageArch.getValue(LINUX_DEB);
var debArch = sysEnv.packageArch().value();
Executor.of(dpkg.toString(), "-S", file.toString())
Executor.of(sysEnv.dpkg().toString(), "-S", file.toString())
.saveOutput(true).executeExpectSuccess()
.getOutput().forEach(line -> {
Matcher matcher = PACKAGE_NAME_REGEX.matcher(line);

View File

@ -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
@ -28,7 +28,7 @@ import static jdk.jpackage.internal.LinuxSystemEnvironment.mixin;
import jdk.jpackage.internal.util.Result;
public interface LinuxDebSystemEnvironment extends LinuxSystemEnvironment, LinuxDebSystemEnvironmentMixin {
interface LinuxDebSystemEnvironment extends LinuxSystemEnvironment, LinuxDebSystemEnvironmentMixin {
static Result<LinuxDebSystemEnvironment> create(Result<LinuxSystemEnvironment> base) {
return mixin(LinuxDebSystemEnvironment.class, base, LinuxDebSystemEnvironmentMixin::create);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -29,7 +29,7 @@ import java.util.Objects;
import java.util.stream.Stream;
import jdk.jpackage.internal.util.Result;
public interface LinuxDebSystemEnvironmentMixin {
interface LinuxDebSystemEnvironmentMixin {
Path dpkg();
Path dpkgdeb();
Path fakeroot();

View File

@ -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
@ -70,9 +70,9 @@ final class LinuxFromOptions {
return LinuxApplication.create(appBuilder.create());
}
static LinuxRpmPackage createLinuxRpmPackage(Options options) {
static LinuxRpmPackage createLinuxRpmPackage(Options options, LinuxRpmSystemEnvironment sysEnv) {
final var superPkgBuilder = createLinuxPackageBuilder(options, LINUX_RPM);
final var superPkgBuilder = createLinuxPackageBuilder(options, sysEnv, LINUX_RPM);
final var pkgBuilder = new LinuxRpmPackageBuilder(superPkgBuilder);
@ -81,9 +81,9 @@ final class LinuxFromOptions {
return pkgBuilder.create();
}
static LinuxDebPackage createLinuxDebPackage(Options options) {
static LinuxDebPackage createLinuxDebPackage(Options options, LinuxDebSystemEnvironment sysEnv) {
final var superPkgBuilder = createLinuxPackageBuilder(options, LINUX_DEB);
final var superPkgBuilder = createLinuxPackageBuilder(options, sysEnv, LINUX_DEB);
final var pkgBuilder = new LinuxDebPackageBuilder(superPkgBuilder);
@ -99,7 +99,7 @@ final class LinuxFromOptions {
return pkg;
}
private static LinuxPackageBuilder createLinuxPackageBuilder(Options options, StandardPackageType type) {
private static LinuxPackageBuilder createLinuxPackageBuilder(Options options, LinuxSystemEnvironment sysEnv, StandardPackageType type) {
final var app = createLinuxApplication(options);
@ -107,6 +107,8 @@ final class LinuxFromOptions {
final var pkgBuilder = new LinuxPackageBuilder(superPkgBuilder);
pkgBuilder.arch(sysEnv.packageArch());
LINUX_PACKAGE_DEPENDENCIES.ifPresentIn(options, pkgBuilder::additionalDependencies);
LINUX_APP_CATEGORY.ifPresentIn(options, pkgBuilder::category);
LINUX_MENU_GROUP.ifPresentIn(options, pkgBuilder::menuGroupName);

View File

@ -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
@ -32,6 +32,7 @@ import java.util.List;
import java.util.Map;
import jdk.jpackage.internal.model.Launcher;
import jdk.jpackage.internal.model.Package;
import jdk.jpackage.internal.util.Enquoter;
/**
* Helper to install launchers as services using "systemd".

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,18 +25,20 @@
package jdk.jpackage.internal;
import java.io.IOException;
import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier;
import java.util.ArrayList;
import jdk.jpackage.internal.model.StandardPackageType;
import jdk.jpackage.internal.util.CommandOutputControl;
import jdk.jpackage.internal.util.Result;
final class LinuxPackageArch {
record LinuxPackageArch(String value) {
static String getValue(StandardPackageType pkgType) {
static Result<LinuxPackageArch> create(StandardPackageType pkgType) {
switch (pkgType) {
case LINUX_RPM -> {
return RpmPackageArch.VALUE;
return rpm().map(LinuxPackageArch::new);
}
case LINUX_DEB -> {
return DebPackageArch.VALUE;
return deb().map(LinuxPackageArch::new);
}
default -> {
throw new IllegalArgumentException();
@ -44,62 +46,51 @@ final class LinuxPackageArch {
}
}
private static class DebPackageArch {
static final String VALUE = toSupplier(DebPackageArch::getValue).get();
private static String getValue() throws IOException {
return Executor.of("dpkg", "--print-architecture").saveOutput(true)
.executeExpectSuccess().getOutput().get(0);
}
private static Result<String> deb() {
var exec = Executor.of("dpkg", "--print-architecture").saveOutput(true);
return Result.of(exec::executeExpectSuccess, IOException.class)
.flatMap(LinuxPackageArch::getStdoutFirstLine);
}
private static class RpmPackageArch {
/*
* Various ways to get rpm arch. Needed to address JDK-8233143. rpmbuild is mandatory for
* rpm packaging, try it first. rpm is optional and may not be available, use as the last
* resort.
*/
private static enum RpmArchReader {
Rpmbuild("rpmbuild", "--eval=%{_target_cpu}"),
Rpm("rpm", "--eval=%{_target_cpu}");
RpmArchReader(String... cmdline) {
this.cmdline = cmdline;
private static Result<String> rpm() {
var errors = new ArrayList<Exception>();
for (var tool : RpmArchReader.values()) {
var result = tool.getRpmArch();
if (result.hasValue()) {
return result;
} else {
errors.addAll(result.errors());
}
String getRpmArch() throws IOException {
Executor exec = Executor.of(cmdline).saveOutput(true);
switch (this) {
case Rpm -> {
exec.executeExpectSuccess();
}
case Rpmbuild -> {
if (exec.execute() != 0) {
return null;
}
}
default -> {
throw new UnsupportedOperationException();
}
}
return exec.getOutput().get(0);
}
private final String[] cmdline;
}
static final String VALUE = toSupplier(RpmPackageArch::getValue).get();
return Result.ofErrors(errors);
}
private static String getValue() throws IOException {
for (var rpmArchReader : RpmArchReader.values()) {
var rpmArchStr = rpmArchReader.getRpmArch();
if (rpmArchStr != null) {
return rpmArchStr;
}
}
throw new RuntimeException("error.rpm-arch-not-detected");
/*
* Various ways to get rpm arch. Needed to address JDK-8233143. rpmbuild is mandatory for
* rpm packaging, try it first. rpm is optional and may not be available, use as the last
* resort.
*/
private enum RpmArchReader {
RPMBUILD("rpmbuild", "--eval=%{_target_cpu}"),
RPM("rpm", "--eval=%{_target_cpu}");
RpmArchReader(String... cmdline) {
this.cmdline = cmdline;
}
Result<String> getRpmArch() {
var exec = Executor.of(cmdline).saveOutput(true);
return Result.of(exec::executeExpectSuccess, IOException.class)
.flatMap(LinuxPackageArch::getStdoutFirstLine);
}
private final String[] cmdline;
}
private static Result<String> getStdoutFirstLine(CommandOutputControl.Result result) {
return Result.of(() -> {
return result.stdout().stream().findFirst().orElseThrow(result::unexpected);
}, IOException.class);
}
}

View File

@ -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
@ -83,7 +83,7 @@ final class LinuxPackageBuilder {
category(),
Optional.ofNullable(additionalDependencies),
release(),
pkg.asStandardPackageType().map(LinuxPackageArch::getValue).orElseThrow()));
arch.value()));
}
LinuxPackageBuilder literalName(String v) {
@ -119,6 +119,11 @@ final class LinuxPackageBuilder {
return Optional.ofNullable(release);
}
LinuxPackageBuilder arch(LinuxPackageArch v) {
arch = v;
return this;
}
private static LinuxApplicationLayout usrTreePackageLayout(Path prefix, String packageName) {
final var lib = prefix.resolve(Path.of("lib", packageName));
return LinuxApplicationLayout.create(
@ -184,6 +189,7 @@ final class LinuxPackageBuilder {
private String category;
private String additionalDependencies;
private String release;
private LinuxPackageArch arch;
private final PackageBuilder pkgBuilder;

View File

@ -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
@ -28,7 +28,7 @@ import static jdk.jpackage.internal.LinuxSystemEnvironment.mixin;
import jdk.jpackage.internal.util.Result;
public interface LinuxRpmSystemEnvironment extends LinuxSystemEnvironment, LinuxRpmSystemEnvironmentMixin {
interface LinuxRpmSystemEnvironment extends LinuxSystemEnvironment, LinuxRpmSystemEnvironmentMixin {
static Result<LinuxRpmSystemEnvironment> create(Result<LinuxSystemEnvironment> base) {
return mixin(LinuxRpmSystemEnvironment.class, base, LinuxRpmSystemEnvironmentMixin::create);

View File

@ -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
@ -32,7 +32,7 @@ import java.util.stream.Stream;
import jdk.jpackage.internal.model.DottedVersion;
import jdk.jpackage.internal.util.Result;
public interface LinuxRpmSystemEnvironmentMixin {
interface LinuxRpmSystemEnvironmentMixin {
Path rpm();
Path rpmbuild();

View File

@ -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,7 +27,6 @@ package jdk.jpackage.internal;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import jdk.jpackage.internal.model.PackageType;
@ -35,9 +34,10 @@ import jdk.jpackage.internal.model.StandardPackageType;
import jdk.jpackage.internal.util.CompositeProxy;
import jdk.jpackage.internal.util.Result;
public interface LinuxSystemEnvironment extends SystemEnvironment {
interface LinuxSystemEnvironment extends SystemEnvironment {
boolean soLookupAvailable();
PackageType nativePackageType();
LinuxPackageArch packageArch();
static Result<LinuxSystemEnvironment> create() {
return detectNativePackageType().map(LinuxSystemEnvironment::create).orElseGet(() -> {
@ -45,7 +45,7 @@ public interface LinuxSystemEnvironment extends SystemEnvironment {
});
}
static Optional<PackageType> detectNativePackageType() {
static Optional<StandardPackageType> detectNativePackageType() {
if (Internal.isDebian()) {
return Optional.of(StandardPackageType.LINUX_DEB);
} else if (Internal.isRpm()) {
@ -55,13 +55,14 @@ public interface LinuxSystemEnvironment extends SystemEnvironment {
}
}
static Result<LinuxSystemEnvironment> create(PackageType nativePackageType) {
return Result.ofValue(new Stub(LibProvidersLookup.supported(),
Objects.requireNonNull(nativePackageType)));
static Result<LinuxSystemEnvironment> create(StandardPackageType nativePackageType) {
return LinuxPackageArch.create(nativePackageType).map(arch -> {
return new Stub(LibProvidersLookup.supported(), nativePackageType, arch);
});
}
static <T, U extends LinuxSystemEnvironment> U createWithMixin(Class<U> type, LinuxSystemEnvironment base, T mixin) {
return CompositeProxy.create(type, base, mixin);
return CompositeProxy.build().invokeTunnel(CompositeProxyTunnel.INSTANCE).create(type, base, mixin);
}
static <T, U extends LinuxSystemEnvironment> Result<U> mixin(Class<U> type,
@ -79,7 +80,7 @@ public interface LinuxSystemEnvironment extends SystemEnvironment {
}
}
record Stub(boolean soLookupAvailable, PackageType nativePackageType) implements LinuxSystemEnvironment {
record Stub(boolean soLookupAvailable, PackageType nativePackageType, LinuxPackageArch packageArch) implements LinuxSystemEnvironment {
}
static final class Internal {

View File

@ -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,8 +24,6 @@
*/
package jdk.jpackage.internal;
import static jdk.jpackage.internal.util.XmlUtils.initDocumentBuilder;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@ -50,7 +48,7 @@ record AppImageInfoPListFile(String bundleIdentifier, String bundleName, String
static AppImageInfoPListFile loadFromInfoPList(Path infoPListFile)
throws IOException, InvalidPlistFileException, SAXException {
final var plistReader = new PListReader(initDocumentBuilder().parse(Files.newInputStream(infoPListFile)));
final var plistReader = new PListReader(Files.readAllBytes(infoPListFile));
try {
return new AppImageInfoPListFile(

View File

@ -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
@ -48,6 +48,7 @@ import jdk.jpackage.internal.model.Launcher;
import jdk.jpackage.internal.model.MacApplication;
import jdk.jpackage.internal.model.RuntimeLayout;
import jdk.jpackage.internal.util.PathUtils;
import jdk.jpackage.internal.util.Result;
import jdk.jpackage.internal.util.function.ExceptionBox;
@ -188,11 +189,9 @@ final class AppImageSigner {
}
private static boolean isXcodeDevToolsInstalled() {
try {
return Executor.of("/usr/bin/xcrun", "--help").setQuiet(true).execute() == 0;
} catch (IOException ex) {
return false;
}
return Result.of(
Executor.of("/usr/bin/xcrun", "--help").setQuiet(true)::executeExpectSuccess,
IOException.class).hasValue();
}
private static void unsign(Path path) throws IOException {

View File

@ -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
@ -34,7 +34,6 @@ import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
public final class Codesign {
@ -94,14 +93,12 @@ public final class Codesign {
public void applyTo(Path path) throws IOException, CodesignException {
var exec = Executor.of(Stream.concat(
cmdline.stream(),
Stream.of(path.toString())).toArray(String[]::new)
).saveOutput(true);
var exec = Executor.of(cmdline).args(path.toString()).saveOutput(true);
configureExecutor.ifPresent(configure -> configure.accept(exec));
if (exec.execute() != 0) {
throw new CodesignException(exec.getOutput().toArray(String[]::new));
var result = exec.execute();
if (result.getExitCode() != 0) {
throw new CodesignException(result.getOutput().toArray(String[]::new));
}
}

View File

@ -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
@ -36,7 +36,6 @@ import java.util.Optional;
import jdk.jpackage.internal.cli.Options;
import jdk.jpackage.internal.model.MacPackage;
import jdk.jpackage.internal.model.Package;
import jdk.jpackage.internal.util.Result;
public class MacBundlingEnvironment extends DefaultBundlingEnvironment {
@ -45,7 +44,7 @@ public class MacBundlingEnvironment extends DefaultBundlingEnvironment {
.defaultOperation(CREATE_MAC_DMG)
.bundler(SIGN_MAC_APP_IMAGE, MacBundlingEnvironment::signAppImage)
.bundler(CREATE_MAC_APP_IMAGE, MacBundlingEnvironment::createAppImage)
.bundler(CREATE_MAC_DMG, LazyLoad::dmgSysEnv, MacBundlingEnvironment::createDmdPackage)
.bundler(CREATE_MAC_DMG, MacDmgSystemEnvironment::create, MacBundlingEnvironment::createDmdPackage)
.bundler(CREATE_MAC_PKG, MacBundlingEnvironment::createPkgPackage));
}
@ -98,13 +97,4 @@ public class MacBundlingEnvironment extends DefaultBundlingEnvironment {
.predefinedAppImageLayout(APPLICATION_LAYOUT)
.predefinedRuntimeImageLayout(MacPackage::guessRuntimeLayout);
}
private static final class LazyLoad {
static Result<MacDmgSystemEnvironment> dmgSysEnv() {
return DMG_SYS_ENV;
}
private static final Result<MacDmgSystemEnvironment> DMG_SYS_ENV = MacDmgSystemEnvironment.create();
}
}

View File

@ -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
@ -53,7 +53,7 @@ public final class MacCertificateUtils {
keychain.map(Keychain::asCliArg).ifPresent(args::add);
return toSupplier(() -> {
final var output = Executor.of(args.toArray(String[]::new))
final var output = Executor.of(args)
.setQuiet(true).saveOutput(true).executeExpectSuccess()
.getOutput();

View File

@ -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
@ -33,11 +33,15 @@ import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import jdk.jpackage.internal.PackagingPipeline.PackageTaskID;
import jdk.jpackage.internal.PackagingPipeline.TaskID;
import jdk.jpackage.internal.model.MacDmgPackage;
@ -105,6 +109,10 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
return env.configDir().resolve(pkg.app().name() + "-license.plist");
}
private Path finalDmg() {
return outputDir.resolve(pkg.packageFileNameWithSuffix());
}
Path protoDmg() {
return dmgWorkdir().resolve("proto.dmg");
}
@ -128,6 +136,10 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
}
}
private Executor hdiutil(String... args) {
return Executor.of(sysEnv.hdiutil().toString()).args(args).storeOutputInFiles();
}
private void prepareDMGSetupScript() throws IOException {
Path dmgSetup = volumeScript();
Log.verbose(MessageFormat.format(
@ -211,13 +223,17 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
}
}
private String hdiUtilVerbosityFlag() {
return env.verbose() ? "-verbose" : "-quiet";
}
private void buildDMG() throws IOException {
boolean copyAppImage = false;
Path protoDMG = protoDmg();
Path finalDMG = outputDir.resolve(pkg.packageFileNameWithSuffix());
final Path protoDMG = protoDmg();
final Path finalDMG = finalDmg();
Path srcFolder = env.appImageDir();
final Path srcFolder = env.appImageDir();
Log.verbose(MessageFormat.format(I18N.getString(
"message.creating-dmg-file"), finalDMG.toAbsolutePath()));
@ -233,21 +249,17 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
Files.createDirectories(protoDMG.getParent());
Files.createDirectories(finalDMG.getParent());
String hdiUtilVerbosityFlag = env.verbose() ?
"-verbose" : "-quiet";
final String hdiUtilVerbosityFlag = hdiUtilVerbosityFlag();
// create temp image
ProcessBuilder pb = new ProcessBuilder(
sysEnv.hdiutil().toString(),
"create",
hdiUtilVerbosityFlag,
"-srcfolder", normalizedAbsolutePathString(srcFolder),
"-volname", volumeName(),
"-ov", normalizedAbsolutePathString(protoDMG),
"-fs", "HFS+",
"-format", "UDRW");
try {
IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT);
hdiutil("create",
hdiUtilVerbosityFlag,
"-srcfolder", normalizedAbsolutePathString(srcFolder),
"-volname", volumeName(),
"-ov", normalizedAbsolutePathString(protoDMG),
"-fs", "HFS+",
"-format", "UDRW").executeExpectSuccess();
} catch (IOException ex) {
Log.verbose(ex); // Log exception
@ -260,31 +272,26 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
// not be bigger, but it will able to hold additional 50 megabytes of data.
// We need extra room for icons and background image. When we providing
// actual files to hdiutil, it will create DMG with ~50 megabytes extra room.
pb = new ProcessBuilder(
sysEnv.hdiutil().toString(),
"create",
hdiUtilVerbosityFlag,
"-size", String.valueOf(size),
"-volname", volumeName(),
"-ov", normalizedAbsolutePathString(protoDMG),
"-fs", "HFS+");
new RetryExecutor()
.setMaxAttemptsCount(10)
.setAttemptTimeoutMillis(3000)
.setWriteOutputToFile(true)
.execute(pb);
hdiutil(
"create",
hdiUtilVerbosityFlag,
"-size", String.valueOf(size),
"-volname", volumeName(),
"-ov", normalizedAbsolutePathString(protoDMG),
"-fs", "HFS+"
).retry()
.setMaxAttemptsCount(10)
.setAttemptTimeout(3, TimeUnit.SECONDS)
.execute();
}
final Path mountedVolume = volumePath();
// mount temp image
pb = new ProcessBuilder(
sysEnv.hdiutil().toString(),
"attach",
hdiutil("attach",
normalizedAbsolutePathString(protoDMG),
hdiUtilVerbosityFlag,
"-mountroot", protoDMG.getParent().toString());
IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT);
final Path mountedVolume = volumePath();
"-mountroot", mountedVolume.getParent().toString()).executeExpectSuccess();
// Copy app image, since we did not create DMG with it, but instead we created
// empty one.
@ -302,9 +309,13 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
// to install-dir in DMG as critical error, since it can fail in
// headless environment.
try {
pb = new ProcessBuilder(sysEnv.osascript().toString(),
normalizedAbsolutePathString(volumeScript()));
IOUtils.exec(pb, 180); // Wait 3 minutes. See JDK-8248248.
Executor.of(
sysEnv.osascript().toString(),
normalizedAbsolutePathString(volumeScript())
)
// Wait 3 minutes. See JDK-8248248.
.timeout(3, TimeUnit.MINUTES)
.executeExpectSuccess();
} catch (IOException ex) {
Log.verbose(ex);
}
@ -325,18 +336,18 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
// but it seems Finder excepts these bytes to be
// "icnC" for the volume icon
// (might not work on Mac 10.13 with old XCode)
pb = new ProcessBuilder(
Executor.of(
sysEnv.setFileUtility().orElseThrow().toString(),
"-c", "icnC",
normalizedAbsolutePathString(volumeIconFile));
IOUtils.exec(pb);
normalizedAbsolutePathString(volumeIconFile)
).executeExpectSuccess();
volumeIconFile.toFile().setReadOnly();
pb = new ProcessBuilder(
Executor.of(
sysEnv.setFileUtility().orElseThrow().toString(),
"-a", "C",
normalizedAbsolutePathString(mountedVolume));
IOUtils.exec(pb);
normalizedAbsolutePathString(mountedVolume)
).executeExpectSuccess();
} catch (IOException ex) {
Log.error(ex.getMessage());
Log.verbose("Cannot enable custom icon using SetFile utility");
@ -347,85 +358,23 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
} finally {
// Detach the temporary image
pb = new ProcessBuilder(
sysEnv.hdiutil().toString(),
"detach",
hdiUtilVerbosityFlag,
normalizedAbsolutePathString(mountedVolume));
// "hdiutil detach" might not work right away due to resource busy error, so
// repeat detach several times.
RetryExecutor retryExecutor = new RetryExecutor();
// Image can get detach even if we got resource busy error, so stop
// trying to detach it if it is no longer attached.
retryExecutor.setExecutorInitializer(exec -> {
if (!Files.exists(mountedVolume)) {
retryExecutor.abort();
}
});
try {
// 10 times with 6 second delays.
retryExecutor.setMaxAttemptsCount(10).setAttemptTimeoutMillis(6000)
.execute(pb);
} catch (IOException ex) {
if (!retryExecutor.isAborted()) {
// Now force to detach if it still attached
if (Files.exists(mountedVolume)) {
pb = new ProcessBuilder(
sysEnv.hdiutil().toString(),
"detach",
"-force",
hdiUtilVerbosityFlag,
normalizedAbsolutePathString(mountedVolume));
IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT);
}
}
}
detachVolume();
}
// Compress it to a new image
pb = new ProcessBuilder(
sysEnv.hdiutil().toString(),
"convert",
normalizedAbsolutePathString(protoDMG),
hdiUtilVerbosityFlag,
"-format", "UDZO",
"-o", normalizedAbsolutePathString(finalDMG));
try {
new RetryExecutor()
.setMaxAttemptsCount(10)
.setAttemptTimeoutMillis(3000)
.execute(pb);
} catch (Exception ex) {
// Convert might failed if something holds file. Try to convert copy.
Path protoCopyDMG = protoCopyDmg();
Files.copy(protoDMG, protoCopyDMG);
try {
pb = new ProcessBuilder(
sysEnv.hdiutil().toString(),
"convert",
normalizedAbsolutePathString(protoCopyDMG),
hdiUtilVerbosityFlag,
"-format", "UDZO",
"-o", normalizedAbsolutePathString(finalDMG));
IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT);
} finally {
Files.deleteIfExists(protoCopyDMG);
}
}
convertProtoDmg();
//add license if needed
if (pkg.licenseFile().isPresent()) {
pb = new ProcessBuilder(
sysEnv.hdiutil().toString(),
hdiutil(
"udifrez",
normalizedAbsolutePathString(finalDMG),
"-xml",
normalizedAbsolutePathString(licenseFile())
);
new RetryExecutor()
.setMaxAttemptsCount(10)
.setAttemptTimeoutMillis(3000)
.execute(pb);
).retry()
.setMaxAttemptsCount(10)
.setAttemptTimeout(3, TimeUnit.SECONDS)
.execute();
}
try {
@ -441,6 +390,69 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir,
}
private void detachVolume() throws IOException {
var mountedVolume = volumePath();
// "hdiutil detach" might not work right away due to resource busy error, so
// repeat detach several times.
Globals.instance().objectFactory().<Void, IOException>retryExecutor(IOException.class).setExecutable(context -> {
List<String> cmdline = new ArrayList<>();
cmdline.add("detach");
if (context.isLastAttempt()) {
// The last attempt, force detach.
cmdline.add("-force");
}
cmdline.addAll(List.of(
hdiUtilVerbosityFlag(),
normalizedAbsolutePathString(mountedVolume)
));
// The image can get detached even if we get a resource busy error,
// so execute the detach command without checking the exit code.
var result = hdiutil(cmdline.toArray(String[]::new)).execute();
if (result.getExitCode() == 0 || !Files.exists(mountedVolume)) {
// Detached successfully!
return null;
} else {
throw result.unexpected();
}
}).setMaxAttemptsCount(10).setAttemptTimeout(6, TimeUnit.SECONDS).execute();
}
private void convertProtoDmg() throws IOException {
Function<Path, Executor> convert = srcDmg -> {
return hdiutil(
"convert",
normalizedAbsolutePathString(srcDmg),
hdiUtilVerbosityFlag(),
"-format", "UDZO",
"-o", normalizedAbsolutePathString(finalDmg()));
};
// Convert it to a new image.
try {
convert.apply(protoDmg()).retry()
.setMaxAttemptsCount(10)
.setAttemptTimeout(3, TimeUnit.SECONDS)
.execute();
} catch (IOException ex) {
Log.verbose(ex);
// Something holds the file, try to convert a copy.
Path copyDmg = protoCopyDmg();
Files.copy(protoDmg(), copyDmg);
try {
convert.apply(copyDmg).executeExpectSuccess();
} finally {
Files.deleteIfExists(copyDmg);
}
}
}
// Background image name in resources
private static final String DEFAULT_BACKGROUND_IMAGE = "background_dmg.tiff";
private static final String DEFAULT_DMG_SETUP_SCRIPT = "DMGsetup.scpt";

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,10 +25,11 @@
package jdk.jpackage.internal;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.jpackage.internal.util.Result;
@ -54,41 +55,31 @@ record MacDmgSystemEnvironment(Path hdiutil, Path osascript, Optional<Path> setF
// Location of SetFile utility may be different depending on MacOS version
// We look for several known places and if none of them work will
// try to find it
private static Optional<Path> findSetFileUtility() {
String typicalPaths[] = {"/Developer/Tools/SetFile",
"/usr/bin/SetFile", "/Developer/usr/bin/SetFile"};
static Optional<Path> findSetFileUtility() {
return SETFILE_KNOWN_PATHS.stream().filter(setFilePath -> {
// Validate SetFile, if Xcode is not installed it will run, but exit with error code
return Result.of(
Executor.of(setFilePath.toString(), "-h").setQuiet(true)::executeExpectSuccess,
IOException.class).hasValue();
}).findFirst().or(() -> {
// generic find attempt
final var executor = Executor.of("/usr/bin/xcrun", "-find", "SetFile").setQuiet(true).saveFirstLineOfOutput();
final var setFilePath = Stream.of(typicalPaths).map(Path::of).filter(Files::isExecutable).findFirst();
if (setFilePath.isPresent()) {
// Validate SetFile, if Xcode is not installed it will run, but exit with error
// code
try {
if (Executor.of(setFilePath.orElseThrow().toString(), "-h").setQuiet(true).execute() == 0) {
return setFilePath;
}
} catch (Exception ignored) {
// No need for generic find attempt. We found it, but it does not work.
// Probably due to missing xcode.
return Optional.empty();
}
}
// generic find attempt
try {
final var executor = Executor.of("/usr/bin/xcrun", "-find", "SetFile");
final var code = executor.setQuiet(true).saveOutput(true).execute();
if (code == 0 && !executor.getOutput().isEmpty()) {
final var firstLine = executor.getOutput().getFirst();
Path f = Path.of(firstLine);
if (new ToolValidator(f).checkExistsOnly().validate() == null) {
return Optional.of(f.toAbsolutePath());
}
}
} catch (IOException ignored) {}
return Optional.empty();
return Result.of(executor::executeExpectSuccess, IOException.class).flatMap(execResult -> {
return Result.of(() -> {
return execResult.stdout().stream().findFirst().map(Path::of).orElseThrow(execResult::unexpected);
}, Exception.class);
}).value().filter(v -> {
return new ToolValidator(v).checkExistsOnly().validate() == null;
}).map(Path::toAbsolutePath);
});
}
static final List<Path> SETFILE_KNOWN_PATHS = Stream.of(
"/Developer/Tools/SetFile",
"/usr/bin/SetFile",
"/Developer/usr/bin/SetFile").map(Path::of).collect(Collectors.toUnmodifiableList());
private static final Path HDIUTIL = Path.of("/usr/bin/hdiutil");
private static final Path OSASCRIPT = Path.of("/usr/bin/osascript");
}

View File

@ -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
@ -32,7 +32,6 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
@ -57,6 +56,7 @@ import jdk.jpackage.internal.PackagingPipeline.PackageTaskID;
import jdk.jpackage.internal.PackagingPipeline.TaskID;
import jdk.jpackage.internal.model.MacPkgPackage;
import jdk.jpackage.internal.resources.ResourceLocator;
import jdk.jpackage.internal.util.Enquoter;
import jdk.jpackage.internal.util.XmlUtils;
import org.xml.sax.SAXException;
@ -108,7 +108,7 @@ record MacPkgPackager(BuildEnv env, MacPkgPackage pkg, Optional<Services> servic
cmdline.addAll(allPkgbuildArgs());
try {
Files.createDirectories(path.getParent());
IOUtils.exec(new ProcessBuilder(cmdline), false, null, true, Executor.INFINITE_TIMEOUT);
Executor.of(cmdline).executeExpectSuccess();
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
@ -487,15 +487,13 @@ record MacPkgPackager(BuildEnv env, MacPkgPackage pkg, Optional<Services> servic
Files.createDirectories(cpl.getParent());
final var pb = new ProcessBuilder("/usr/bin/pkgbuild",
Executor.of("/usr/bin/pkgbuild",
"--root",
normalizedAbsolutePathString(env.appImageDir()),
"--install-location",
normalizedAbsolutePathString(installLocation()),
"--analyze",
normalizedAbsolutePathString(cpl));
IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT);
normalizedAbsolutePathString(cpl)).executeExpectSuccess();
patchCPLFile(cpl);
}
@ -544,8 +542,7 @@ record MacPkgPackager(BuildEnv env, MacPkgPackage pkg, Optional<Services> servic
}
commandLine.add(normalizedAbsolutePathString(finalPkg));
final var pb = new ProcessBuilder(commandLine);
IOUtils.exec(pb, false, null, true, Executor.INFINITE_TIMEOUT);
Executor.of(commandLine).executeExpectSuccess();
}
private static Optional<Services> createServices(BuildEnv env, MacPkgPackage pkg) {

View File

@ -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,15 +27,17 @@ package jdk.jpackage.internal;
import java.io.Closeable;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import jdk.internal.util.OSVersion;
import jdk.jpackage.internal.util.function.ThrowingConsumer;
final class TempKeychain implements Closeable {
static void withKeychains(ThrowingConsumer<List<Keychain>, ? extends Exception> keychainConsumer, List<Keychain> keychains) throws Exception {
static void withKeychains(Consumer<List<Keychain>> keychainConsumer, List<Keychain> keychains) {
keychains.forEach(Objects::requireNonNull);
if (keychains.isEmpty() || OSVersion.current().compareTo(new OSVersion(10, 12)) < 0) {
keychainConsumer.accept(keychains);
@ -43,11 +45,14 @@ final class TempKeychain implements Closeable {
// we need this for OS X 10.12+
try (var tempKeychain = new TempKeychain(keychains)) {
keychainConsumer.accept(tempKeychain.keychains);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}
static void withKeychain(ThrowingConsumer<Keychain, ? extends Exception> keychainConsumer, Keychain keychain) throws Exception {
static void withKeychain(Consumer<Keychain> keychainConsumer, Keychain keychain) {
Objects.requireNonNull(keychainConsumer);
withKeychains(keychains -> {
keychainConsumer.accept(keychains.getFirst());
@ -78,7 +83,7 @@ final class TempKeychain implements Closeable {
args.addAll(missingKeychains.stream().map(Keychain::asCliArg).toList());
Executor.of(args.toArray(String[]::new)).executeExpectSuccess();
Executor.of(args).executeExpectSuccess();
}
}
@ -89,7 +94,7 @@ final class TempKeychain implements Closeable {
@Override
public void close() throws IOException {
if (!restoreKeychainsCmd.isEmpty()) {
Executor.of(restoreKeychainsCmd.toArray(String[]::new)).executeExpectSuccess();
Executor.of(restoreKeychainsCmd).executeExpectSuccess();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -28,11 +28,12 @@ import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toUnmodifiableMap;
import static java.util.stream.Collectors.toUnmodifiableSet;
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.APP_VERSION;
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_AS_SERVICE;
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.DESCRIPTION;
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_AS_SERVICE;
import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_NAME;
import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
@ -162,7 +163,23 @@ final class AppImageFile {
final var relativeAppImageFilePath = appImageDir.relativize(appImageFilePath);
try {
final Document doc = XmlUtils.initDocumentBuilder().parse(Files.newInputStream(appImageFilePath));
//
// Use javax.xml.parsers.DocumentBuilder#parse(java.io.InputStream).
// Don't use javax.xml.parsers.DocumentBuilder#parse(java.io.File) as this will introduce
// dependency on how the XML parser reports filesystem I/O errors.
// E.g.: the default JDK XML parser throws java.io.FileNotFoundException if the supplied
// directory is not found and throws org.xml.sax.SAXParseException if the supplied file is a directory.
// Another DOM XML parser (a different version of Xerces?) may behave differently.
//
// The use of javax.xml.parsers.DocumentBuilder#parse(java.io.InputStream) eliminates
// differences in how XML parsers handle file system I/O errors.
// Filesystem I/O is delegated to java.nio.file.Files#readAllBytes(java.nio.file.Path),
// XML parser deals with the byte stream in memory and the error handling code
// doesn't depend on how XML parser reports filesystem I/O errors because
// it reads data from memory, not from the filesystem.
//
final Document doc = XmlUtils.initDocumentBuilder().parse(
new ByteArrayInputStream(Files.readAllBytes(appImageFilePath)));
final XPath xPath = XPathFactory.newInstance().newXPath();

View File

@ -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
@ -65,10 +65,10 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
Map<BundlingOperationDescriptor, Supplier<Result<Consumer<Options>>>> bundlers) {
this.bundlers = bundlers.entrySet().stream().collect(toMap(Map.Entry::getKey, e -> {
return new CachingSupplier<>(e.getValue());
return runOnce(e.getValue());
}));
this.defaultOperationSupplier = Objects.requireNonNull(defaultOperationSupplier).map(CachingSupplier::new);
this.defaultOperationSupplier = Objects.requireNonNull(defaultOperationSupplier).map(DefaultBundlingEnvironment::runOnce);
}
@ -98,6 +98,11 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
return bundler(op, () -> Result.ofValue(bundler));
}
Builder mutate(Consumer<Builder> mutator) {
mutator.accept(this);
return this;
}
private Supplier<Optional<BundlingOperationDescriptor>> defaultOperationSupplier;
private final Map<BundlingOperationDescriptor, Supplier<Result<Consumer<Options>>>> bundlers = new HashMap<>();
}
@ -107,6 +112,10 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
return new Builder();
}
static <T> Supplier<T> runOnce(Supplier<T> supplier) {
return new CachingSupplier<>(supplier);
}
static <T extends SystemEnvironment> Supplier<Result<Consumer<Options>>> createBundlerSupplier(
Supplier<Result<T>> sysEnvResultSupplier, BiConsumer<Options, T> bundler) {
Objects.requireNonNull(sysEnvResultSupplier);
@ -279,5 +288,5 @@ class DefaultBundlingEnvironment implements CliBundlingEnvironment {
private final Map<BundlingOperationDescriptor, Supplier<Result<Consumer<Options>>>> bundlers;
private final Optional<CachingSupplier<Optional<BundlingOperationDescriptor>>> defaultOperationSupplier;
private final Optional<Supplier<Optional<BundlingOperationDescriptor>>> defaultOperationSupplier;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,53 +25,153 @@
package jdk.jpackage.internal;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.PrintStream;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.spi.ToolProvider;
import java.util.stream.Stream;
import jdk.jpackage.internal.util.CommandLineFormat;
import jdk.jpackage.internal.util.CommandOutputControl;
import jdk.jpackage.internal.util.CommandOutputControl.ProcessAttributes;
import jdk.jpackage.internal.util.CommandOutputControl.Result;
import jdk.jpackage.internal.util.RetryExecutor;
import jdk.jpackage.internal.util.function.ExceptionBox;
public final class Executor {
final class Executor {
Executor() {
static Executor of(String... cmdline) {
return of(List.of(cmdline));
}
Executor setOutputConsumer(Consumer<Stream<String>> v) {
outputConsumer = v;
return this;
static Executor of(List<String> cmdline) {
return of(new ProcessBuilder(cmdline));
}
static Executor of(ProcessBuilder pb) {
return Globals.instance().objectFactory().executor().processBuilder(pb);
}
public Executor() {
commandOutputControl = new CommandOutputControl();
args = new ArrayList<>();
}
private Executor(Executor other) {
commandOutputControl = other.commandOutputControl.copy();
quietCommand = other.quietCommand;
args = new ArrayList<>(other.args);
processBuilder = other.processBuilder;
toolProvider = other.toolProvider;
timeout = other.timeout;
mapper = other.mapper;
}
Executor saveOutput(boolean v) {
saveOutput = v;
commandOutputControl.saveOutput(v);
return this;
}
Executor setWriteOutputToFile(boolean v) {
writeOutputToFile = v;
Executor saveOutput() {
return saveOutput(true);
}
Executor saveFirstLineOfOutput() {
commandOutputControl.saveFirstLineOfOutput();
return this;
}
Executor setTimeout(long v) {
Executor charset(Charset v) {
commandOutputControl.charset(v);
return this;
}
Executor storeOutputInFiles(boolean v) {
commandOutputControl.storeOutputInFiles(v);
return this;
}
Executor storeOutputInFiles() {
return storeOutputInFiles(true);
}
Executor binaryOutput(boolean v) {
commandOutputControl.binaryOutput(v);
return this;
}
Executor binaryOutput() {
return binaryOutput(true);
}
Executor discardStdout(boolean v) {
commandOutputControl.discardStdout(v);
return this;
}
Executor discardStdout() {
return discardStdout(true);
}
Executor discardStderr(boolean v) {
commandOutputControl.discardStderr(v);
return this;
}
Executor discardStderr() {
return discardStderr(true);
}
Executor timeout(long v, TimeUnit unit) {
return timeout(Duration.of(v, unit.toChronoUnit()));
}
Executor timeout(Duration v) {
timeout = v;
if (timeout != INFINITE_TIMEOUT) {
// Redirect output to file if timeout is requested, otherwise we will
// reading until process ends and timeout will never be reached.
setWriteOutputToFile(true);
}
return this;
}
Executor setProcessBuilder(ProcessBuilder v) {
pb = v;
Executor toolProvider(ToolProvider v) {
toolProvider = Objects.requireNonNull(v);
processBuilder = null;
return this;
}
Executor setCommandLine(String... cmdline) {
return setProcessBuilder(new ProcessBuilder(cmdline));
Optional<ToolProvider> toolProvider() {
return Optional.ofNullable(toolProvider);
}
Executor processBuilder(ProcessBuilder v) {
processBuilder = Objects.requireNonNull(v);
toolProvider = null;
return this;
}
Optional<ProcessBuilder> processBuilder() {
return Optional.ofNullable(processBuilder);
}
Executor args(List<String> v) {
args.addAll(v);
return this;
}
Executor args(String... args) {
return args(List.of(args));
}
List<String> args() {
return args;
}
Executor setQuiet(boolean v) {
@ -79,159 +179,207 @@ public final class Executor {
return this;
}
List<String> getOutput() {
return output;
}
Executor executeExpectSuccess() throws IOException {
int ret = execute();
if (0 != ret) {
throw new IOException(
String.format("Command %s exited with %d code",
createLogMessage(pb, false), ret));
}
Executor mapper(UnaryOperator<Executor> v) {
mapper = v;
return this;
}
int execute() throws IOException {
output = null;
Optional<UnaryOperator<Executor>> mapper() {
return Optional.ofNullable(mapper);
}
boolean needProcessOutput = outputConsumer != null || Log.isVerbose() || saveOutput;
Path outputFile = null;
if (needProcessOutput) {
pb.redirectErrorStream(true);
if (writeOutputToFile) {
outputFile = Files.createTempFile("jpackageOutputTempFile", ".tmp");
pb.redirectOutput(outputFile.toFile());
Executor copy() {
return new Executor(this);
}
Result execute() throws IOException {
if (mapper != null) {
var mappedExecutor = Objects.requireNonNull(mapper.apply(this));
if (mappedExecutor != this) {
return mappedExecutor.execute();
}
}
var coc = commandOutputControl.copy();
final CommandOutputControl.Executable exec;
if (processBuilder != null) {
exec = coc.createExecutable(copyProcessBuilder());
} else if (toolProvider != null) {
exec = coc.createExecutable(toolProvider, args.toArray(String[]::new));
} else {
// We are not going to read process output, so need to notify
// ProcessBuilder about this. Otherwise some processes might just
// hang up (`ldconfig -p`).
pb.redirectError(ProcessBuilder.Redirect.DISCARD);
pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
throw new IllegalStateException("No target to execute");
}
if (!quietCommand) {
Log.verbose(String.format("Running %s", createLogMessage(pb, true)));
PrintableOutputBuilder printableOutputBuilder;
if (dumpOutput()) {
printableOutputBuilder = new PrintableOutputBuilder(coc);
} else {
printableOutputBuilder = null;
}
Process p = pb.start();
int code = 0;
if (writeOutputToFile) {
try {
code = waitForProcess(p);
} catch (InterruptedException ex) {
Log.verbose(ex);
throw new RuntimeException(ex);
}
}
if (needProcessOutput) {
final List<String> savedOutput;
Supplier<Stream<String>> outputStream;
if (writeOutputToFile) {
output = savedOutput = Files.readAllLines(outputFile);
Files.delete(outputFile);
outputStream = () -> {
if (savedOutput != null) {
return savedOutput.stream();
}
return null;
};
if (outputConsumer != null) {
outputConsumer.accept(outputStream.get());
}
} else {
try (var br = new BufferedReader(new InputStreamReader(
p.getInputStream()))) {
if ((outputConsumer != null || Log.isVerbose())
|| saveOutput) {
savedOutput = br.lines().toList();
} else {
savedOutput = null;
}
output = savedOutput;
outputStream = () -> {
if (savedOutput != null) {
return savedOutput.stream();
}
return br.lines();
};
if (outputConsumer != null) {
outputConsumer.accept(outputStream.get());
}
if (savedOutput == null) {
// For some processes on Linux if the output stream
// of the process is opened but not consumed, the process
// would exit with code 141.
// It turned out that reading just a single line of process
// output fixes the problem, but let's process
// all of the output, just in case.
br.lines().forEach(x -> {});
}
}
}
if (dumpOutput()) {
Log.verbose(String.format("Running %s", CommandLineFormat.DEFAULT.apply(List.of(commandLine().getFirst()))));
}
Result result;
try {
if (!writeOutputToFile) {
code = p.waitFor();
}
if (!quietCommand) {
Log.verbose(pb.command(), getOutput(), code, IOUtils.getPID(p));
}
return code;
} catch (InterruptedException ex) {
Log.verbose(ex);
throw new RuntimeException(ex);
}
}
private int waitForProcess(Process p) throws InterruptedException {
if (timeout == INFINITE_TIMEOUT) {
return p.waitFor();
} else {
if (p.waitFor(timeout, TimeUnit.SECONDS)) {
return p.exitValue();
if (timeout == null) {
result = exec.execute();
} else {
Log.verbose(String.format("Command %s timeout after %d seconds",
createLogMessage(pb, false), timeout));
p.destroy();
return -1;
result = exec.execute(timeout.toMillis(), TimeUnit.MILLISECONDS);
}
} catch (InterruptedException ex) {
throw ExceptionBox.toUnchecked(ex);
}
if (dumpOutput()) {
log(result, printableOutputBuilder.create());
}
return result;
}
Result executeExpectSuccess() throws IOException {
return execute().expectExitCode(0);
}
Result executeExpect(int mainExitCode, int... otherExitCodes) throws IOException {
return execute().expectExitCode(mainExitCode, otherExitCodes);
}
RetryExecutor<Result, IOException> retry() {
return Globals.instance().objectFactory().<Result, IOException>retryExecutor(IOException.class)
.setExecutable(this::executeExpectSuccess);
}
RetryExecutor<Result, IOException> retryOnKnownErrorMessage(String msg) {
Objects.requireNonNull(msg);
return saveOutput().retry().setExecutable(() -> {
// Execute it without exit code check.
var result = execute();
if (result.stderr().stream().anyMatch(msg::equals)) {
throw result.unexpected();
}
return result;
});
}
List<String> commandLine() {
if (processBuilder != null) {
return Stream.of(processBuilder.command(), args).flatMap(Collection::stream).toList();
} else if (toolProvider != null) {
return Stream.concat(Stream.of(toolProvider.name()), args.stream()).toList();
} else {
throw new IllegalStateException("No target to execute");
}
}
private ProcessBuilder copyProcessBuilder() {
if (processBuilder == null) {
throw new IllegalStateException();
}
var copy = new ProcessBuilder(commandLine());
copy.directory(processBuilder.directory());
var env = copy.environment();
env.clear();
env.putAll(processBuilder.environment());
return copy;
}
private boolean dumpOutput() {
return Log.isVerbose() && !quietCommand;
}
private static void log(Result result, String printableOutput) throws IOException {
Objects.requireNonNull(result);
Objects.requireNonNull(printableOutput);
Optional<Long> pid;
if (result.execAttrs() instanceof ProcessAttributes attrs) {
pid = attrs.pid();
} else {
pid = Optional.empty();
}
var sb = new StringBuilder();
sb.append("Command");
pid.ifPresent(p -> {
sb.append(" [PID: ").append(p).append("]");
});
sb.append(":\n ").append(result.execAttrs());
Log.verbose(sb.toString());
if (!printableOutput.isEmpty()) {
sb.delete(0, sb.length());
sb.append("Output:");
try (var lines = new BufferedReader(new StringReader(printableOutput)).lines()) {
lines.forEach(line -> {
sb.append("\n ").append(line);
});
}
Log.verbose(sb.toString());
}
result.exitCode().ifPresentOrElse(exitCode -> {
Log.verbose("Returned: " + exitCode + "\n");
}, () -> {
Log.verbose("Aborted: timed-out" + "\n");
});
}
private static final class PrintableOutputBuilder {
PrintableOutputBuilder(CommandOutputControl coc) {
coc.dumpOutput(true);
charset = coc.charset();
if (coc.isBinaryOutput()) {
// Assume binary output goes into stdout and text error messages go into stderr, so keep them separated.
sinks = new ByteArrayOutputStream[2];
sinks[0] = new ByteArrayOutputStream();
sinks[1] = new ByteArrayOutputStream();
coc.dumpStdout(new PrintStream(sinks[0], false, charset))
.dumpStderr(new PrintStream(sinks[1], false, charset));
} else {
sinks = new ByteArrayOutputStream[1];
sinks[0] = new ByteArrayOutputStream();
var ps = new PrintStream(sinks[0], false, charset);
// Redirect stderr in stdout.
coc.dumpStdout(ps).dumpStderr(ps);
}
}
}
static Executor of(String... cmdline) {
return new Executor().setCommandLine(cmdline);
}
static Executor of(ProcessBuilder pb) {
return new Executor().setProcessBuilder(pb);
}
private static String createLogMessage(ProcessBuilder pb, boolean quiet) {
StringBuilder sb = new StringBuilder();
sb.append((quiet) ? pb.command().get(0) : pb.command());
if (pb.directory() != null) {
sb.append(String.format(" in %s", pb.directory().getAbsolutePath()));
String create() {
if (isBinaryOutput()) {
// In case of binary output:
// - Convert binary stdout to text using ISO-8859-1 encoding and
// replace non-printable characters with the question mark symbol (?).
// - Convert binary stderr to text using designated encoding (assume stderr is always a character stream).
// - Merge text stdout and stderr into a single string;
// stderr first, stdout follows, with the aim to present user error messages first.
var sb = new StringBuilder();
var stdout = sinks[0].toString(StandardCharsets.ISO_8859_1).replaceAll("[^\\p{Print}\\p{Space}]", "?");
return sb.append(sinks[1].toString(charset)).append(stdout).toString();
} else {
return sinks[0].toString(charset);
}
}
return sb.toString();
private boolean isBinaryOutput() {
return sinks.length == 2;
}
private final ByteArrayOutputStream sinks[];
private final Charset charset;
}
public static final int INFINITE_TIMEOUT = -1;
private ProcessBuilder pb;
private boolean saveOutput;
private boolean writeOutputToFile;
private final CommandOutputControl commandOutputControl;
private boolean quietCommand;
private long timeout = INFINITE_TIMEOUT;
private List<String> output;
private Consumer<Stream<String>> outputConsumer;
private final List<String> args;
private ProcessBuilder processBuilder;
private ToolProvider toolProvider;
private Duration timeout;
private UnaryOperator<Executor> mapper;
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jpackage.internal;
@FunctionalInterface
interface ExecutorFactory {
Executor executor();
static final ExecutorFactory DEFAULT = Executor::new;
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jpackage.internal;
import java.util.Optional;
import java.util.function.Supplier;
public final class Globals {
private Globals() {
}
Globals objectFactory(ObjectFactory v) {
checkMutable();
objectFactory = Optional.ofNullable(v).orElse(ObjectFactory.DEFAULT);
return this;
}
ObjectFactory objectFactory() {
return objectFactory;
}
Globals executorFactory(ExecutorFactory v) {
return objectFactory(ObjectFactory.build(objectFactory).executorFactory(v).create());
}
public static int main(Supplier<Integer> mainBody) {
if (INSTANCE.isBound()) {
return mainBody.get();
} else {
return ScopedValue.where(INSTANCE, new Globals()).call(mainBody::get);
}
}
public static Globals instance() {
return INSTANCE.orElse(DEFAULT);
}
private void checkMutable() {
if (this == DEFAULT) {
throw new UnsupportedOperationException("Can't modify immutable instance");
}
}
private ObjectFactory objectFactory = ObjectFactory.DEFAULT;
private static final ScopedValue<Globals> INSTANCE = ScopedValue.newInstance();
private static final Globals DEFAULT = new Globals();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,12 +26,9 @@
package jdk.jpackage.internal;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import jdk.jpackage.internal.model.JPackageException;
/**
@ -50,46 +47,6 @@ final class IOUtils {
StandardCopyOption.COPY_ATTRIBUTES);
}
public static void exec(ProcessBuilder pb)
throws IOException {
exec(pb, false, null, false, Executor.INFINITE_TIMEOUT);
}
// timeout in seconds. -1 will be return if process timeouts.
public static void exec(ProcessBuilder pb, long timeout)
throws IOException {
exec(pb, false, null, false, timeout);
}
static void exec(ProcessBuilder pb, boolean testForPresenceOnly,
PrintStream consumer, boolean writeOutputToFile, long timeout)
throws IOException {
exec(pb, testForPresenceOnly, consumer, writeOutputToFile,
timeout, false);
}
static void exec(ProcessBuilder pb, boolean testForPresenceOnly,
PrintStream consumer, boolean writeOutputToFile,
long timeout, boolean quiet) throws IOException {
List<String> output = new ArrayList<>();
Executor exec = Executor.of(pb)
.setWriteOutputToFile(writeOutputToFile)
.setTimeout(timeout)
.setQuiet(quiet)
.setOutputConsumer(lines -> {
lines.forEach(output::add);
if (consumer != null) {
output.forEach(consumer::println);
}
});
if (testForPresenceOnly) {
exec.execute();
} else {
exec.executeExpectSuccess();
}
}
static void writableOutputDir(Path outdir) {
if (!Files.isDirectory(outdir)) {
try {
@ -103,15 +60,4 @@ final class IOUtils {
throw new JPackageException(I18N.format("error.cannot-write-to-output-dir", outdir.toAbsolutePath()));
}
}
public static long getPID(Process p) {
try {
return p.pid();
} catch (UnsupportedOperationException ex) {
Log.verbose(ex); // Just log exception and ignore it. This method
// is used for verbose output, so not a problem
// if unsupported.
return -1;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -22,13 +22,14 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jpackage.internal;
import static jdk.jpackage.internal.model.RuntimeBuilder.getDefaultModulePath;
import static jdk.jpackage.internal.util.function.ThrowingRunnable.toRunnable;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
@ -50,7 +51,6 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.internal.module.ModulePath;
import jdk.jpackage.internal.model.AppImageLayout;
import jdk.jpackage.internal.model.JPackageException;
import jdk.jpackage.internal.model.LauncherModularStartupInfo;
import jdk.jpackage.internal.model.LauncherStartupInfo;
import jdk.jpackage.internal.model.RuntimeBuilder;
@ -58,27 +58,15 @@ import jdk.jpackage.internal.model.RuntimeBuilder;
final class JLinkRuntimeBuilder implements RuntimeBuilder {
private JLinkRuntimeBuilder(List<String> jlinkCmdLine) {
this.jlinkCmdLine = jlinkCmdLine;
this.jlinkCmdLine = Objects.requireNonNull(jlinkCmdLine);
}
@Override
public void create(AppImageLayout appImageLayout) {
var args = new ArrayList<String>();
args.add("--output");
args.add(appImageLayout.runtimeDirectory().toString());
args.addAll(jlinkCmdLine);
StringWriter writer = new StringWriter();
PrintWriter pw = new PrintWriter(writer);
int retVal = LazyLoad.JLINK_TOOL.run(pw, pw, args.toArray(String[]::new));
String jlinkOut = writer.toString();
args.add(0, "jlink");
Log.verbose(args, List.of(jlinkOut), retVal, -1);
if (retVal != 0) {
throw new JPackageException(I18N.format("error.jlink.failed", jlinkOut));
}
toRunnable(Executor.of()
.toolProvider(LazyLoad.JLINK_TOOL)
.args("--output", appImageLayout.runtimeDirectory().toString())
.args(jlinkCmdLine)::executeExpectSuccess).run();
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -28,7 +28,6 @@ package jdk.jpackage.internal;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
/**
* Log
@ -105,29 +104,6 @@ public class Log {
}
}
public void verbose(List<String> strings,
List<String> output, int returnCode, long pid) {
if (verbose) {
StringBuilder sb = new StringBuilder();
sb.append("Command [PID: ");
sb.append(pid);
sb.append("]:\n ");
for (String s : strings) {
sb.append(" " + s);
}
verbose(sb.toString());
if (output != null && !output.isEmpty()) {
sb = new StringBuilder("Output:");
for (String s : output) {
sb.append("\n " + s);
}
verbose(sb.toString());
}
verbose("Returned: " + returnCode + "\n");
}
}
private String addTimestamp(String msg) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
Date time = new Date(System.currentTimeMillis());
@ -177,9 +153,4 @@ public class Log {
public static void verbose(Throwable t) {
instance.get().verbose(t);
}
public static void verbose(List<String> strings, List<String> out,
int ret, long pid) {
instance.get().verbose(strings, out, ret, pid);
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jpackage.internal;
import java.util.Objects;
import java.util.Optional;
import jdk.jpackage.internal.util.CompositeProxy;
interface ObjectFactory extends ExecutorFactory, RetryExecutorFactory {
static ObjectFactory.Builder build() {
return new Builder();
}
static ObjectFactory.Builder build(ObjectFactory from) {
return build().initFrom(from);
}
static final class Builder {
private Builder() {
}
ObjectFactory create() {
return CompositeProxy.build().invokeTunnel(CompositeProxyTunnel.INSTANCE).create(
ObjectFactory.class,
Optional.ofNullable(executorFactory).orElse(ExecutorFactory.DEFAULT),
Optional.ofNullable(retryExecutorFactory).orElse(RetryExecutorFactory.DEFAULT));
}
Builder initFrom(ObjectFactory of) {
Objects.requireNonNull(of);
return executorFactory(of).retryExecutorFactory(of);
}
Builder executorFactory(ExecutorFactory v) {
executorFactory = v;
return this;
}
Builder retryExecutorFactory(RetryExecutorFactory v) {
retryExecutorFactory = v;
return this;
}
private ExecutorFactory executorFactory;
private RetryExecutorFactory retryExecutorFactory;
}
static final ObjectFactory DEFAULT = build().create();
}

View File

@ -1,136 +0,0 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jpackage.internal;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
public final class RetryExecutor {
public RetryExecutor() {
setMaxAttemptsCount(5);
setAttemptTimeoutMillis(2 * 1000);
setWriteOutputToFile(false);
}
public RetryExecutor setMaxAttemptsCount(int v) {
attempts = v;
return this;
}
public RetryExecutor setAttemptTimeoutMillis(int v) {
timeoutMillis = v;
return this;
}
public RetryExecutor saveOutput(boolean v) {
saveOutput = v;
return this;
}
public List<String> getOutput() {
return output;
}
public RetryExecutor setWriteOutputToFile(boolean v) {
writeOutputToFile = v;
return this;
}
public RetryExecutor setExecutorInitializer(Consumer<Executor> v) {
executorInitializer = v;
return this;
}
public void abort() {
aborted = true;
}
public boolean isAborted() {
return aborted;
}
static RetryExecutor retryOnKnownErrorMessage(String v) {
RetryExecutor result = new RetryExecutor();
return result.setExecutorInitializer(exec -> {
exec.setOutputConsumer(output -> {
if (!output.anyMatch(v::equals)) {
result.abort();
}
});
});
}
public void execute(String cmdline[]) throws IOException {
executeLoop(() ->
Executor.of(cmdline).setWriteOutputToFile(writeOutputToFile));
}
public void execute(ProcessBuilder pb) throws IOException {
executeLoop(() ->
Executor.of(pb).setWriteOutputToFile(writeOutputToFile));
}
private void executeLoop(Supplier<Executor> execSupplier) throws IOException {
aborted = false;
for (;;) {
if (aborted) {
break;
}
try {
Executor exec = execSupplier.get().saveOutput(saveOutput);
if (executorInitializer != null) {
executorInitializer.accept(exec);
}
exec.executeExpectSuccess();
if (saveOutput) {
output = exec.getOutput();
}
break;
} catch (IOException ex) {
if (aborted || (--attempts) <= 0) {
throw ex;
}
}
try {
Thread.sleep(timeoutMillis);
} catch (InterruptedException ex) {
Log.verbose(ex);
throw new RuntimeException(ex);
}
}
}
private Consumer<Executor> executorInitializer;
private boolean aborted;
private int attempts;
private int timeoutMillis;
private boolean saveOutput;
private List<String> output;
private boolean writeOutputToFile;
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jpackage.internal;
import jdk.jpackage.internal.util.RetryExecutor;
@FunctionalInterface
interface RetryExecutorFactory {
<T, E extends Exception> RetryExecutor<T, E> retryExecutor(Class<? extends E> exceptionType);
static final RetryExecutorFactory DEFAULT = RetryExecutor::new;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -24,5 +24,5 @@
*/
package jdk.jpackage.internal;
public interface SystemEnvironment {
interface SystemEnvironment {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -121,32 +121,32 @@ final class ToolValidator {
cmdline.addAll(args);
}
boolean canUseTool[] = new boolean[1];
boolean canUseTool = false;
if (minimalVersion == null) {
// No version check.
canUseTool[0] = true;
canUseTool = true;
}
String[] version = new String[1];
String version = null;
try {
Executor.of(cmdline.toArray(String[]::new)).setQuiet(true).setOutputConsumer(lines -> {
if (versionParser != null && minimalVersion != null) {
version[0] = versionParser.apply(lines);
if (version[0] != null && minimalVersion.compareTo(version[0]) <= 0) {
canUseTool[0] = true;
}
var result = Executor.of(cmdline).setQuiet(true).saveOutput().execute();
var lines = result.content();
if (versionParser != null && minimalVersion != null) {
version = versionParser.apply(lines.stream());
if (version != null && minimalVersion.compareTo(version) <= 0) {
canUseTool = true;
}
}).execute();
}
} catch (IOException e) {
return new ConfigException(I18N.format("error.tool-error", toolPath, e.getMessage()), null, e);
}
if (canUseTool[0]) {
if (canUseTool) {
// All good. Tool can be used.
return null;
} else if (toolOldVersionErrorHandler != null) {
return toolOldVersionErrorHandler.apply(toolPath, version[0]);
return toolOldVersionErrorHandler.apply(toolPath, version);
} else {
return new ConfigException(
I18N.format("error.tool-old-version", toolPath, minimalVersion),

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -45,6 +45,7 @@ import java.util.function.Supplier;
import java.util.spi.ToolProvider;
import jdk.internal.opt.CommandLine;
import jdk.internal.util.OperatingSystem;
import jdk.jpackage.internal.Globals;
import jdk.jpackage.internal.Log;
import jdk.jpackage.internal.model.ConfigException;
import jdk.jpackage.internal.model.JPackageException;
@ -56,7 +57,15 @@ import jdk.jpackage.internal.util.function.ExceptionBox;
*/
public final class Main {
public static final class Provider implements ToolProvider {
public record Provider(Supplier<CliBundlingEnvironment> bundlingEnvSupplier) implements ToolProvider {
public Provider {
Objects.requireNonNull(bundlingEnvSupplier);
}
public Provider() {
this(DefaultBundlingEnvironmentLoader.INSTANCE);
}
@Override
public String name() {
@ -65,7 +74,7 @@ public final class Main {
@Override
public int run(PrintWriter out, PrintWriter err, String... args) {
return Main.run(out, err, args);
return Main.run(bundlingEnvSupplier, out, err, args);
}
@Override
@ -94,7 +103,23 @@ public final class Main {
System.exit(run(out, err, args));
}
public static int run(PrintWriter out, PrintWriter err, String... args) {
static int run(PrintWriter out, PrintWriter err, String... args) {
return run(DefaultBundlingEnvironmentLoader.INSTANCE, out, err, args);
}
static int run(Supplier<CliBundlingEnvironment> bundlingEnvSupplier, PrintWriter out, PrintWriter err, String... args) {
return Globals.main(() -> {
return runWithGlobals(bundlingEnvSupplier, out, err, args);
});
}
private static int runWithGlobals(
Supplier<CliBundlingEnvironment> bundlingEnvSupplier,
PrintWriter out,
PrintWriter err,
String... args) {
Objects.requireNonNull(bundlingEnvSupplier);
Objects.requireNonNull(args);
for (String arg : args) {
Objects.requireNonNull(arg);
@ -128,8 +153,7 @@ public final class Main {
return preprocessStatus;
}
final var bundlingEnv = ServiceLoader.load(CliBundlingEnvironment.class,
CliBundlingEnvironment.class.getClassLoader()).findFirst().orElseThrow();
final var bundlingEnv = bundlingEnvSupplier.get();
final var parseResult = Utils.buildParser(OperatingSystem.current(), bundlingEnv).create().apply(mappedArgs.get());
@ -285,4 +309,15 @@ public final class Main {
private static String getVersion() {
return System.getProperty("java.version");
}
private enum DefaultBundlingEnvironmentLoader implements Supplier<CliBundlingEnvironment> {
INSTANCE;
@Override
public CliBundlingEnvironment get() {
return ServiceLoader.load(
CliBundlingEnvironment.class,
CliBundlingEnvironment.class.getClassLoader()).findFirst().orElseThrow();
}
}
}

View File

@ -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
@ -96,7 +96,6 @@ 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.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

View File

@ -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<String> 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<List<String>, String> DEFAULT = platform()::format;
}

View File

@ -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<Integer, StringBuilder> v) {
public Enquoter setEscaper(BiConsumer<Integer, StringBuilder> v) {
escaper = v;
return this;
}
Enquoter setEnquotePredicate(Predicate<String> v) {
public Enquoter setEnquotePredicate(Predicate<String> v) {
needQuotes = v;
return this;
}
private int beginQuoteChr;
private int endQuoteChr;
private BiConsumer<Integer, StringBuilder> escaper;
private Predicate<String> needQuotes = str -> false;
private static final Predicate<String> QUOTE_IF_WHITESPACES = new Predicate<String>() {
public static final Predicate<String> QUOTE_IF_WHITESPACES = new Predicate<String>() {
@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<Integer, StringBuilder> PREPEND_BACKSLASH = (chr, buf) -> {
public static final BiConsumer<Integer, StringBuilder> PREPEND_BACKSLASH = (chr, buf) -> {
buf.append('\\');
buf.appendCodePoint(chr);
};
private int beginQuoteChr;
private int endQuoteChr;
private BiConsumer<Integer, StringBuilder> escaper;
private Predicate<String> needQuotes = str -> false;
}

View File

@ -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)));
}

View File

@ -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<T, E extends Exception> {
public RetryExecutor(Class<? extends E> exceptionType) {
this.exceptionType = Objects.requireNonNull(exceptionType);
setMaxAttemptsCount(5);
setAttemptTimeout(2, TimeUnit.SECONDS);
}
final public Class<? extends E> exceptionType() {
return exceptionType;
}
public RetryExecutor<T, E> setExecutable(ThrowingFunction<Context<RetryExecutor<T, E>>, T, E> v) {
executable = v;
return this;
}
final public RetryExecutor<T, E> setExecutable(ThrowingSupplier<T, E> v) {
if (v != null) {
setExecutable(_ -> {
return v.get();
});
} else {
executable = null;
}
return this;
}
public RetryExecutor<T, E> setMaxAttemptsCount(int v) {
attempts = v;
return this;
}
final public RetryExecutor<T, E> setAttemptTimeout(long v, TimeUnit unit) {
return setAttemptTimeout(Duration.of(v, unit.toChronoUnit()));
}
public RetryExecutor<T, E> setAttemptTimeout(Duration v) {
timeout = v;
return this;
}
public RetryExecutor<T, E> setExceptionMapper(Function<E, RuntimeException> v) {
toUnchecked = v;
return this;
}
public RetryExecutor<T, E> setSleepFunction(Consumer<Duration> v) {
sleepFunction = v;
return this;
}
final public RetryExecutor<T, E> mutate(Consumer<RetryExecutor<T, E>> 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<T> {
boolean isLastAttempt();
int attempt();
T executor();
}
private final class DefaultContext implements Context<RetryExecutor<T, E>>, Iterator<Void> {
@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<T, E> executor() {
return RetryExecutor.this;
}
private int attempt = -1;
}
private ThrowingFunction<Context<RetryExecutor<T, E>>, 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<? extends E> exceptionType;
private ThrowingFunction<Context<RetryExecutor<T, E>>, T, E> executable;
private int attempts;
private Duration timeout;
private Function<E, RuntimeException> toUnchecked;
private Consumer<Duration> sleepFunction;
}

View File

@ -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<OutputStream> 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<OutputStream, IOException> 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<OutputStream> items;
}

View File

@ -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.

View File

@ -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
@ -31,16 +31,17 @@ 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) {
@ -98,12 +99,4 @@ public class WinBundlingEnvironment extends DefaultBundlingEnvironment {
}
}
private static final class LazyLoad {
static Result<WinSystemEnvironment> sysEnv() {
return SYS_ENV;
}
private static final Result<WinSystemEnvironment> SYS_ENV = WinSystemEnvironment.create();
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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("<html>");
this.pw.println("<style>\n"
+ "div { display:none;}\n"
+ "</style>\n"
+ "\n"
+ "<script>\n"
+ "function show(e) {\n"
+ " while (e != null) {\n"
+ " if (e.tagName == 'DIV') {\n"
+ " e.style.display = 'block';\n"
+ " }\n"
+ " e = e.parentNode;\n"
+ " }\n"
+ "}\n"
+ "\n"
+ "function toggle(id) {\n"
+ " e = document.getElementById(id);\n"
+ " d = e.style.display;\n"
+ " if (d == 'block') {\n"
+ " e.style.display = 'none';\n"
+ " } else {\n"
+ " show(e);\n"
+ " }\n"
+ "}\n"
+ "\n"
+ "function main() {\n"
+ " index = location.href.indexOf(\"#\");"
+ " if (index != -1) {\n"
+ " show(document.getElementById(location.href.substring(index + 1)));\n"
+ " }\n"
+ "}\n"
+ "\n"
+ "</script>\n"
+ "</head>");
this.pw.println("<body onload='main()'>");
this.pw.println("<head>");
this.pw.println(
"<link href=\"" + HtmlPage.STYLE_SHEET_FILENAME + "\" rel=\"stylesheet\" type=\"text/css\" />");
this.pw.println(
"<script src=\"" + HtmlPage.SCRIPT_FILENAME + "\" type=\"text/javascript\" ></script>");
this.pw.println("</head>");
this.pw.println("<body>");
} else {
this.rootSection = rootSection;
this.pw.print("<ul>");
@ -146,7 +120,7 @@ public class HtmlSection {
} else if (child != null) {
path = String.format("%s.%s", path, child);
}
pw.printf("<a href=\"#%1$s\" onclick=\"show(document.getElementById('%1$s')); return true;\">%2$s</a>%n",
pw.printf("<a href=\"#%1$s\" data-show=\"%1$s\" >%2$s</a>%n",
path, name);
}
@ -188,7 +162,7 @@ public class HtmlSection {
: String.format("%s.%s", parent.id, name),
name, rootSection);
this.parent = parent;
pw.printf("<li><a name='%1$s'/><a href='#%1$s' onclick=\"toggle('%1$s'); return false;\">%2$s</a><div id='%1$s'><code><pre>",
pw.printf("<li><a name='%1$s'/><a href='#%1$s' data-toggle=\"%1$s\" >%2$s</a><div id='%1$s'><code><pre>",
id, name);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2021, 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
@ -105,9 +105,7 @@ public class GatherDiagnosticInfoObserver implements Harness.Observer {
private void gatherCoreInfo(Path workDir, String name, Path core, PrintWriter log,
CoreInfoGatherer gatherer) {
File output = workDir.resolve(CORES_OUTPUT).toFile();
try (HtmlPage html = new HtmlPage(new PrintWriter(
new FileWriter(output, true), true))) {
try (HtmlPage html = new HtmlPage(workDir, CORES_OUTPUT, true)) {
try (ElapsedTimePrinter timePrinter
= new ElapsedTimePrinter(new Stopwatch(), name, log)) {
gatherer.gatherCoreInfo(html.getRootSection(), core);
@ -121,9 +119,7 @@ public class GatherDiagnosticInfoObserver implements Harness.Observer {
private void gatherEnvInfo(Path workDir, String name, PrintWriter log,
EnvironmentInfoGatherer gatherer) {
File output = workDir.resolve(ENVIRONMENT_OUTPUT).toFile();
try (HtmlPage html = new HtmlPage(new PrintWriter(
new FileWriter(output, true), true))) {
try (HtmlPage html = new HtmlPage(workDir, ENVIRONMENT_OUTPUT, true)) {
try (ElapsedTimePrinter timePrinter
= new ElapsedTimePrinter(new Stopwatch(), name, log)) {
gatherer.gatherEnvironmentInfo(html.getRootSection());

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -69,16 +69,7 @@ public class GatherProcessInfoTimeoutHandler extends TimeoutHandler {
}
try {
actionsLog.printf("%s ---%n", name);
File output = workDir.resolve(OUTPUT_FILENAME).toFile();
try {
PrintWriter pw = new PrintWriter(new FileWriter(output, true), true);
runGatherer(name, workDir, actionsLog, pw, pid);
} catch (IOException e) {
actionsLog.printf("IOException: cannot open output file[%s] : %s",
output, e.getMessage());
e.printStackTrace(actionsLog);
}
runGatherer(name, actionsLog, pid);
} finally {
actionsLog.printf("--- %s%n", name);
// don't close jtreg log
@ -90,9 +81,9 @@ public class GatherProcessInfoTimeoutHandler extends TimeoutHandler {
}
}
private void runGatherer(String name, Path workDir, PrintWriter log,
PrintWriter out, long pid) {
try (HtmlPage html = new HtmlPage(out)) {
private void runGatherer(String name, PrintWriter log, long pid) {
Path workDir = outputDir.toPath();
try (HtmlPage html = new HtmlPage(workDir, OUTPUT_FILENAME, true)) {
ProcessInfoGatherer gatherer = new GathererFactory(
OS.current().family,
workDir, log, testJdk.toPath()).getProcessInfoGatherer();

View File

@ -162,6 +162,35 @@ TEST_VM(AtomicIntegerTest, cmpxchg_int64) {
Support().test();
}
template<typename T>
struct AtomicIntegerCmpsetTestSupport {
Atomic<T> _test_value;
AtomicIntegerCmpsetTestSupport() : _test_value{} {}
void test() {
T zero = 0;
T five = 5;
T ten = 10;
_test_value.store_relaxed(zero);
EXPECT_FALSE(_test_value.compare_set(five, ten));
EXPECT_EQ(zero, _test_value.load_relaxed());
EXPECT_TRUE(_test_value.compare_set(zero, ten));
EXPECT_EQ(ten, _test_value.load_relaxed());
}
};
TEST_VM(AtomicIntegerTest, cmpset_int32) {
using Support = AtomicIntegerCmpsetTestSupport<int32_t>;
Support().test();
}
TEST_VM(AtomicIntegerTest, cmpset_int64) {
// Check if 64-bit atomics are available on the machine.
using Support = AtomicIntegerCmpsetTestSupport<int64_t>;
Support().test();
}
struct AtomicXchgAndCmpxchg1ByteStressSupport {
char _default_val;
int _base;

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* 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 compiler.c2.igvn;
/*
* @test
* @bug 8372302
* @summary ModINode::Ideal and ModLNode::Ideal use an intermediate "hook" node
* to keep stuff alive between phase->transform(...) calls. In some cases,
* this node is not properly deleted before returning, causing failure
* in the verification because the node count has changed. This test
* ensures that the intermediate node gets destroyed before returning.
* @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions
* -Xcomp -XX:-TieredCompilation
* -XX:CompileCommand=compileonly,${test.main.class}::test*
* -XX:VerifyIterativeGVN=1110
* ${test.main.class}
* @run main ${test.main.class}
*
*/
public class TestModIdealCreatesUselessNode {
static int test0(int x) {
return x % Integer.MIN_VALUE;
}
static long test1(long x) {
return x % Long.MIN_VALUE;
}
public static void main(String[] args) {
test0(0);
test1(0L);
}
}

View File

@ -274,6 +274,7 @@ public final class Operations {
ops.add(Expression.make(BOOLEANS, "Boolean.logicalXor(", BOOLEANS, ", ", BOOLEANS, ")"));
// TODO: Math and other classes.
// Note: Math.copySign is non-deterministic because of NaN having encoding with sign bit set and unset.
// Make sure the list is not modifiable.
return List.copyOf(ops);
@ -294,7 +295,8 @@ public final class Operations {
ops.add(Expression.make(INTS, "Float16.compare(", FLOAT16, ",", FLOAT16, ")"));
addComparisonOperations(ops, "Float16.compare", FLOAT16);
ops.add(Expression.make(INTS, "(", FLOAT16, ").compareTo(", FLOAT16, ")"));
ops.add(Expression.make(FLOAT16, "Float16.copySign(", FLOAT16, ",", FLOAT16, ")"));
// Note: There are NaN encodings with bit set or unset.
ops.add(Expression.make(FLOAT16, "Float16.copySign(", FLOAT16, ",", FLOAT16, ")", WITH_NONDETERMINISTIC_RESULT));
ops.add(Expression.make(FLOAT16, "Float16.divide(", FLOAT16, ",", FLOAT16, ")"));
ops.add(Expression.make(BOOLEANS, "", FLOAT16, ".equals(", FLOAT16, ")"));
// Note: there are multiple NaN values with different bit representations.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -45,8 +45,9 @@ abstract class TestTask implements Runnable {
}
public void ensureReadyAndWaiting(Thread vt, Thread.State expState, ReentrantLock rlock) {
sleep(50); // reliability: wait for a potential class loading to complete
// wait while the thread is not ready or thread state is unexpected
while (!threadReady || (vt.getState() != expState) || !rlock.hasQueuedThreads()) {
while (!threadReady || (vt.getState() != expState) || !rlock.hasQueuedThread(vt)) {
sleep(1);
}
}
@ -125,11 +126,12 @@ public class ThreadListStackTracesTest {
int jvmtiExpState = (expState == Thread.State.WAITING) ?
JVMTI_THREAD_STATE_WAITING :
JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
Thread.State state = vt.getState();
System.out.printf("State: expected: %s single: %x multi: %x\n",
vt.getState(), singleState, multiState);
System.out.printf("State: expected: %s, vt.getState(): %s, jvmtiExpState: %x single: %x multi: %x\n",
expState, state, jvmtiExpState, singleState, multiState);
if (vt.getState() != expState) {
if (state != expState) {
failed("Java thread state is wrong");
}
if ((singleState & jvmtiExpState) == 0) {

Some files were not shown because too many files have changed in this diff Show More