001/* 002 * Copyright 2017-2026 original authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.micronaut.maven.jdkaotcache; 017 018import io.micronaut.core.annotation.Internal; 019import org.apache.maven.artifact.Artifact; 020import org.apache.maven.plugin.MojoExecutionException; 021import org.apache.maven.plugin.logging.Log; 022import org.jspecify.annotations.Nullable; 023 024import java.util.Collection; 025import java.util.List; 026 027/** 028 * The JDK AOT cache training run of an application: the mode, and what ends the run. It is resolved from 029 * {@code micronaut.docker.jdkAotCache.trainingMode}, the training paths and what the application's Micronaut version can 030 * do. 031 * 032 * <p>Without a configured mode, the run is a {@code LOAD} run if the Micronaut version has that mode, because it creates 033 * no bean and so needs none of the services the beans use, which an image build does not have. Otherwise, it starts the 034 * application, as every Micronaut version can.</p> 035 * 036 * @author Álvaro Sánchez-Mariscal 037 * @since 5.1.0 038 */ 039@Internal 040public final class TrainingRun { 041 042 private static final String SCRIPT_LOAD = "load"; 043 private static final String SCRIPT_SWITCH = "switch"; 044 private static final String SCRIPT_SIGTERM = "sigterm"; 045 private static final String LOG_PREFIX = "JDK AOT cache: "; 046 private static final String PATHS_OPTION = "micronaut.docker.jdkAotCache.trainingPaths"; 047 private static final String LOAD_OPTION = TrainingMode.LOAD.option(); 048 private static final String START_OPTION = TrainingMode.START.option(); 049 private static final String LOAD_SENDS_NO_REQUESTS = "a LOAD training run does not start the application, so it " 050 + "sends no requests."; 051 052 private final TrainingMode mode; 053 private final boolean usesSwitch; 054 private final String description; 055 private final @Nullable String warning; 056 057 /** 058 * @param mode how far the application goes 059 * @param usesSwitch whether the Micronaut training-run switch ends the run. Otherwise, the training script warms the 060 * application up and stops it with SIGTERM 061 * @param description what the run does and why, for the build log 062 * @param warning what the build has to change before the application moves to a Micronaut version with the load 063 * mode, or {@code null} if nothing 064 */ 065 TrainingRun(TrainingMode mode, boolean usesSwitch, String description, @Nullable String warning) { 066 this.mode = mode; 067 this.usesSwitch = usesSwitch; 068 this.description = description; 069 this.warning = warning; 070 } 071 072 /** 073 * A training run with nothing to warn about. 074 * 075 * @param mode how far the application goes 076 * @param usesSwitch whether the Micronaut training-run switch ends the run 077 * @param description what the run does and why, for the build log 078 */ 079 TrainingRun(TrainingMode mode, boolean usesSwitch, String description) { 080 this(mode, usesSwitch, description, null); 081 } 082 083 /** 084 * @param configured the configured {@code micronaut.docker.jdkAotCache.trainingMode}, or {@code null} if it is not 085 * set 086 * @param trainingPaths the validated training paths 087 * @param artifacts the resolved dependencies of the application 088 * @return the training run 089 * @throws MojoExecutionException if the application's Micronaut version cannot run the mode, or if training paths 090 * are set for a run that does not start the application 091 */ 092 public static TrainingRun resolve(@Nullable TrainingMode configured, List<String> trainingPaths, 093 Collection<Artifact> artifacts) throws MojoExecutionException { 094 boolean warmUp = !trainingPaths.isEmpty(); 095 boolean loadAvailable = TrainingRunSwitch.hasLoadMode(artifacts); 096 if (configured == TrainingMode.LOAD) { 097 return configuredLoad(warmUp, loadAvailable); 098 } 099 if (configured == null && loadAvailable) { 100 if (warmUp) { 101 throw new MojoExecutionException(PATHS_OPTION + " is set, but the training mode is LOAD, the default " 102 + "with this Micronaut version: " + LOAD_SENDS_NO_REQUESTS + " Set " + START_OPTION + " if the " 103 + "application can start in the image build, or remove the training paths."); 104 } 105 return new TrainingRun(TrainingMode.LOAD, true, "training mode LOAD, the default: the application loads " 106 + "its bean definitions and exits without starting, so the training needs none of the services that " 107 + "its beans use. If the application can start in the image build, " + START_OPTION + " trains a more " 108 + "complete cache"); 109 } 110 boolean usesSwitch = TrainingRunSwitch.isAvailable(artifacts, warmUp); 111 if (configured != null) { 112 return new TrainingRun(TrainingMode.START, usesSwitch, "training mode START: the training run starts the " 113 + "application"); 114 } 115 // The build that sets training paths and no mode is the one that fails after a Micronaut upgrade 116 String warning = warmUp ? PATHS_OPTION + " is set and " + TrainingMode.PARAMETER + " is not. This build will fail " 117 + "once the application uses a Micronaut version with the LOAD training mode: LOAD becomes the default, " 118 + "and " + LOAD_SENDS_NO_REQUESTS + " Set " + START_OPTION + " now to keep starting the application" : null; 119 return new TrainingRun(TrainingMode.START, usesSwitch, "training mode START: the training run starts the " 120 + "application, because its Micronaut version has no training mode that loads it without starting it (" 121 + TrainingRunSwitch.MODE_PROPERTY + "). The services it needs at start-up must be reachable from the image " 122 + "build. With a Micronaut version that has that mode, LOAD becomes the default: set " + START_OPTION 123 + " to keep starting the application", warning); 124 } 125 126 /** 127 * An explicit {@code LOAD} fails on a Micronaut version without the mode, instead of starting an application that 128 * was declared unable to start. That is checked before the training paths, and its message covers them, so that 129 * a build with both problems is told about both at once. 130 */ 131 private static TrainingRun configuredLoad(boolean warmUp, boolean loadAvailable) throws MojoExecutionException { 132 if (!loadAvailable) { 133 throw new MojoExecutionException(LOAD_OPTION + " needs a Micronaut version with the LOAD training mode (" 134 + TrainingRunSwitch.MODE_PROPERTY + "), which the application's Micronaut version does not have. " 135 + "Upgrade Micronaut, or remove the option: the training run then starts the application." 136 + (warmUp ? " If you upgrade, remove " + PATHS_OPTION + " as well: " + LOAD_SENDS_NO_REQUESTS : "")); 137 } 138 if (warmUp) { 139 throw new MojoExecutionException(PATHS_OPTION + " cannot be used with " + LOAD_OPTION + ": " 140 + LOAD_SENDS_NO_REQUESTS + " Remove the training paths, or set " + START_OPTION + " if the application " 141 + "can start in the image build."); 142 } 143 return new TrainingRun(TrainingMode.LOAD, true, "training mode LOAD: the application loads its bean " 144 + "definitions and exits without starting"); 145 } 146 147 /** 148 * @return how far the application goes 149 */ 150 TrainingMode mode() { 151 return mode; 152 } 153 154 /** 155 * @return whether the Micronaut training-run switch ends the run. Otherwise, the training script warms the 156 * application up and stops it with SIGTERM 157 */ 158 public boolean usesSwitch() { 159 return usesSwitch; 160 } 161 162 /** 163 * @return what the run does and why, for the build log 164 */ 165 String description() { 166 return description; 167 } 168 169 /** 170 * @return what the build has to change before the application moves to a Micronaut version with the load mode, or 171 * {@code null} if nothing 172 */ 173 @Nullable String warning() { 174 return warning; 175 } 176 177 /** 178 * Writes what the run does and why to the build log, and the warning, if there is one. 179 * 180 * @param log the Maven log 181 */ 182 public void log(Log log) { 183 log.info(LOG_PREFIX + description); 184 if (warning != null) { 185 log.warn(LOG_PREFIX + warning); 186 } 187 } 188 189 /** 190 * Writes what {@link #log(Log)} writes and, for a run that starts the application, how the training script of the 191 * generated Dockerfile ends it. 192 * 193 * @param log the Maven log 194 */ 195 public void logGeneratedDockerfileRun(Log log) { 196 log(log); 197 if (mode == TrainingMode.START) { 198 log.info(LOG_PREFIX + (usesSwitch 199 ? "the application warms itself up and exits (Micronaut training-run switch)" 200 : "the training script warms the application up and stops it with SIGTERM")); 201 } 202 } 203 204 /** 205 * @param trainingPaths the validated training paths 206 * @return the Java system properties of the training JVM: those of the Micronaut training-run switch when it ends 207 * the run, none when the training script does 208 */ 209 List<String> systemProperties(List<String> trainingPaths) { 210 return usesSwitch ? TrainingRunSwitch.systemProperties(mode, trainingPaths) : List.of(); 211 } 212 213 /** 214 * @return the argument that tells the training script how to run the application: {@code load}, {@code switch} or 215 * {@code sigterm} 216 */ 217 public String scriptArgument() { 218 if (mode == TrainingMode.LOAD) { 219 return SCRIPT_LOAD; 220 } 221 return usesSwitch ? SCRIPT_SWITCH : SCRIPT_SIGTERM; 222 } 223}