diff --git a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java index c35a0befbbef0..713580b28d7cc 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java @@ -1,5 +1,6 @@ package io.quarkus.maven; +import java.io.IOException; import java.lang.reflect.Method; import java.nio.file.Path; import java.util.List; @@ -16,6 +17,7 @@ import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.bootstrap.model.ApplicationModel; +import io.quarkus.bootstrap.util.BootstrapUtils; import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.paths.PathCollection; import io.quarkus.paths.PathList; @@ -31,8 +33,11 @@ public class GenerateCodeMojo extends QuarkusBootstrapMojo { * Skip the execution of this mojo */ @Parameter(defaultValue = "false", property = "quarkus.generate-code.skip", alias = "quarkus.prepare.skip") - private boolean skipSourceGeneration = false; + boolean skipSourceGeneration = false; + /** + * Application launch mode for which to generate the source code. + */ @Parameter(defaultValue = "NORMAL", property = "launchMode") String mode; @@ -57,7 +62,7 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException { void generateCode(PathCollection sourceParents, Consumer sourceRegistrar, - boolean test) throws MojoFailureException, MojoExecutionException { + boolean test) throws MojoExecutionException { final LaunchMode launchMode; if (test) { @@ -97,13 +102,31 @@ void generateCode(PathCollection sourceParents, if (deploymentClassLoader != null) { deploymentClassLoader.close(); } - // in case of test mode, we can't share the bootstrapped app with the testing plugins, so we are closing it right away + // In case of the test mode, we can't share the application model with the test plugins, so we are closing it right away, + // but we are serializing the application model so the test plugins can deserialize it from disk instead of re-initializing + // the resolver and re-resolving it as part of the test bootstrap if (test && curatedApplication != null) { - curatedApplication.close(); + var appModel = curatedApplication.getApplicationModel(); + closeApplication(LaunchMode.TEST); + if (isSerializeTestModel()) { + final int workspaceId = getWorkspaceId(); + if (workspaceId != 0) { + try { + BootstrapUtils.writeAppModelWithWorkspaceId(appModel, workspaceId, BootstrapUtils + .getSerializedTestAppModelPath(Path.of(mavenProject().getBuild().getDirectory()))); + } catch (IOException e) { + getLog().warn("Failed to serialize application model", e); + } + } + } } } } + protected boolean isSerializeTestModel() { + return false; + } + protected PathCollection getParentDirs(List sourceDirs) { if (sourceDirs.size() == 1) { return PathList.of(Path.of(sourceDirs.get(0)).getParent()); diff --git a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java index b644af7be0a18..ed5b9bd71e6a4 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java @@ -13,15 +13,29 @@ import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import io.quarkus.bootstrap.app.CuratedApplication; +import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.builder.Json; import io.quarkus.maven.dependency.ResolvedDependency; import io.quarkus.runtime.LaunchMode; @Mojo(name = "generate-code-tests", defaultPhase = LifecyclePhase.GENERATE_TEST_SOURCES, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, threadSafe = true) public class GenerateCodeTestsMojo extends GenerateCodeMojo { + + /** + * A switch that enables or disables serialization of an {@link ApplicationModel} to a file for tests. + * Deserializing an application model when bootstrapping Quarkus tests has a performance advantage in that + * the tests will not have to initialize a Maven resolver and re-resolve the application model, which may save, + * depending on a project, ~80-95% of time on {@link ApplicationModel} resolution. + *

+ * Serialization of the test model is enabled by default. + */ + @Parameter(property = "quarkus.generate-code.serialize-test-model", defaultValue = "true") + boolean serializeTestModel; + @Override protected void doExecute() throws MojoExecutionException, MojoFailureException { generateCode(getParentDirs(mavenProject().getTestCompileSourceRoots()), @@ -32,6 +46,11 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException { } } + @Override + protected boolean isSerializeTestModel() { + return serializeTestModel; + } + private boolean isTestWithNativeAgent() { String value = System.getProperty("quarkus.test.integration-test-profile"); if ("test-with-native-agent".equals(value)) { diff --git a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java index b8cfa1396f9b3..fe53cd99d3433 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java @@ -296,6 +296,20 @@ protected CuratedApplication bootstrapApplication(LaunchMode mode) throws MojoEx return bootstrapProvider.bootstrapApplication(this, mode); } + protected void closeApplication(LaunchMode mode) { + bootstrapProvider.closeApplication(this, mode); + } + + /** + * Workspace ID associated with a given bootstrap mojo. + * If the returned value is {@code 0}, a workspace was not associated with the bootstrap mojo. + * + * @return workspace ID associated with a given bootstrap mojo + */ + protected int getWorkspaceId() { + return bootstrapProvider.getWorkspaceId(this); + } + protected CuratedApplication bootstrapApplication(LaunchMode mode, Consumer builderCustomizer) throws MojoExecutionException { return bootstrapProvider.bootstrapApplication(this, mode, builderCustomizer); diff --git a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java index a2b09b2e7a06c..1341a7d14c9eb 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java @@ -45,6 +45,7 @@ import io.quarkus.bootstrap.resolver.maven.EffectiveModelResolver; import io.quarkus.bootstrap.resolver.maven.IncubatingApplicationModelResolver; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; +import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; import io.quarkus.maven.components.ManifestSection; import io.quarkus.maven.components.QuarkusWorkspaceProvider; import io.quarkus.maven.dependency.ArtifactCoords; @@ -138,6 +139,21 @@ public CuratedApplication bootstrapApplication(QuarkusBootstrapMojo mojo, Launch return bootstrapper(mojo).bootstrapApplication(mojo, mode, builderCustomizer); } + public void closeApplication(QuarkusBootstrapMojo mojo, LaunchMode mode) { + bootstrapper(mojo).closeApplication(mode); + } + + /** + * Workspace ID associated with a given bootstrap mojo. + * If the returned value is {@code 0}, a workspace was not associated with the bootstrap mojo. + * + * @param mojo bootstrap mojo + * @return workspace ID associated with a given bootstrap mojo + */ + public int getWorkspaceId(QuarkusBootstrapMojo mojo) { + return bootstrapper(mojo).workspaceId; + } + public ApplicationModel getResolvedApplicationModel(ArtifactKey projectId, LaunchMode mode, String bootstrapId) { if (appBootstrapProviders.size() == 0) { return null; @@ -180,6 +196,7 @@ private static boolean isWorkspaceDiscovery(QuarkusBootstrapMojo mojo) { public class QuarkusMavenAppBootstrap implements Closeable { + private int workspaceId; private CuratedApplication prodApp; private CuratedApplication devApp; private CuratedApplication testApp; @@ -187,7 +204,7 @@ public class QuarkusMavenAppBootstrap implements Closeable { private MavenArtifactResolver artifactResolver(QuarkusBootstrapMojo mojo, LaunchMode mode) { try { if (mode == LaunchMode.DEVELOPMENT || mode == LaunchMode.TEST || isWorkspaceDiscovery(mojo)) { - return workspaceProvider.createArtifactResolver( + var resolver = workspaceProvider.createArtifactResolver( BootstrapMavenContext.config() // it's important to pass user settings in case the process was not launched using the original mvn script // for example using org.codehaus.plexus.classworlds.launcher.Launcher @@ -199,6 +216,11 @@ private MavenArtifactResolver artifactResolver(QuarkusBootstrapMojo mojo, Launch .setRemoteRepositories(mojo.remoteRepositories()) .setEffectiveModelBuilder(BootstrapMavenContextConfig .getEffectiveModelBuilderProperty(mojo.mavenProject().getProperties()))); + final LocalProject currentProject = resolver.getMavenContext().getCurrentProject(); + if (currentProject != null && workspaceId == 0) { + workspaceId = currentProject.getWorkspace().getId(); + } + return resolver; } // PROD packaging mode with workspace discovery disabled return MavenArtifactResolver.builder() @@ -376,6 +398,23 @@ protected CuratedApplication bootstrapApplication(QuarkusBootstrapMojo mojo, Lau return prodApp == null ? prodApp = doBootstrap(mojo, mode, builderCustomizer) : prodApp; } + protected void closeApplication(LaunchMode mode) { + if (mode == LaunchMode.DEVELOPMENT) { + if (devApp != null) { + devApp.close(); + devApp = null; + } + } else if (mode == LaunchMode.TEST) { + if (testApp != null) { + testApp.close(); + testApp = null; + } + } else if (prodApp != null) { + prodApp.close(); + prodApp = null; + } + } + protected ArtifactCoords managingProject(QuarkusBootstrapMojo mojo) { if (mojo.appArtifactCoords() == null) { return null; diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/BootstrapUtils.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/BootstrapUtils.java index 43602918e8e46..21659ce8d9569 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/BootstrapUtils.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/BootstrapUtils.java @@ -9,14 +9,22 @@ import java.nio.file.Path; import java.util.regex.Pattern; +import org.jboss.logging.Logger; + import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.resolver.AppModelResolverException; import io.quarkus.maven.dependency.ArtifactKey; +import io.quarkus.maven.dependency.DependencyFlags; import io.quarkus.maven.dependency.GACT; +import io.quarkus.maven.dependency.ResolvedDependency; public class BootstrapUtils { + private static final Logger log = Logger.getLogger(BootstrapUtils.class); + + private static final int CP_CACHE_FORMAT_ID = 2; + private static Pattern splitByWs; public static String[] splitByWhitespace(String s) { @@ -81,7 +89,90 @@ public static ApplicationModel deserializeQuarkusModel(Path modelPath) throws Ap throw new AppModelResolverException("Unable to locate quarkus model"); } + /** + * Returns a location where a serialized {@link ApplicationModel} would be found for dev mode. + * + * @param projectBuildDir project build directory + * @return file of a serialized application model for dev mode + */ public static Path resolveSerializedAppModelPath(Path projectBuildDir) { - return projectBuildDir.resolve("quarkus").resolve("bootstrap").resolve("dev-app-model.dat"); + return getBootstrapBuildDir(projectBuildDir).resolve("dev-app-model.dat"); + } + + /** + * Returns a location where a serialized {@link ApplicationModel} would be found for test mode. + * + * @param projectBuildDir project build directory + * @return file of a serialized application model for test mode + */ + public static Path getSerializedTestAppModelPath(Path projectBuildDir) { + return getBootstrapBuildDir(projectBuildDir).resolve("test-app-model.dat"); + } + + private static Path getBootstrapBuildDir(Path projectBuildDir) { + return projectBuildDir.resolve("quarkus").resolve("bootstrap"); + } + + /** + * Serializes an {@link ApplicationModel} along with the workspace ID for which it was resolved. + * The serialization format will be different from the one used by {@link #resolveSerializedAppModelPath(Path)} + * and {@link #getSerializedTestAppModelPath(Path)}. + * + * @param appModel application model to serialize + * @param workspaceId workspace ID + * @param file target file + * @throws IOException in case of an IO failure + */ + public static void writeAppModelWithWorkspaceId(ApplicationModel appModel, int workspaceId, Path file) throws IOException { + Files.createDirectories(file.getParent()); + try (ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(file))) { + out.writeInt(CP_CACHE_FORMAT_ID); + out.writeInt(workspaceId); + out.writeObject(appModel); + } + log.debugf("Serialized application model to %s", file); + } + + /** + * Deserializes an {@link ApplicationModel} from a file. + *

+ * The implementation will check whether the serialization format of the file matches the expected one. + * If it does not, the method will return null even if the file exists. + *

+ * The implementation will compare the deserialized workspace ID to the argument {@code workspaceId} + * and if they don't match the method will return null. + *

+ * Once the {@link ApplicationModel} was deserialized, the dependency paths will be checked for existence. + * If a dependency path does not exist, the method will throw an exception. + * + * @param file serialized application model file + * @param workspaceId expected workspace ID + * @return deserialized application model + * @throws ClassNotFoundException in case a required class could not be loaded + * @throws IOException in case of an IO failure + */ + public static ApplicationModel readAppModelWithWorkspaceId(Path file, int workspaceId) + throws ClassNotFoundException, IOException { + try (ObjectInputStream reader = new ObjectInputStream(Files.newInputStream(file))) { + if (reader.readInt() == CP_CACHE_FORMAT_ID) { + if (reader.readInt() == workspaceId) { + final ApplicationModel appModel = (ApplicationModel) reader.readObject(); + log.debugf("Loaded application model %s from %s", appModel, file); + for (ResolvedDependency d : appModel.getDependencies(DependencyFlags.DEPLOYMENT_CP)) { + for (Path p : d.getResolvedPaths()) { + if (!Files.exists(p)) { + throw new IOException("Cached artifact does not exist: " + p); + } + } + } + return appModel; + } else { + log.debugf("Application model saved in %s has a different workspace ID", file); + } + } else { + log.debugf("Unsupported application model serialization format in %s", file); + } + } + return null; } } diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java index a748ea5a4b732..3d8a4c4dd2f78 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java @@ -1,11 +1,11 @@ package io.quarkus.bootstrap; -import java.io.DataInputStream; -import java.io.DataOutputStream; +import static io.quarkus.bootstrap.util.BootstrapUtils.readAppModelWithWorkspaceId; +import static io.quarkus.bootstrap.util.BootstrapUtils.writeAppModelWithWorkspaceId; + import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -30,6 +30,7 @@ import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; import io.quarkus.bootstrap.resolver.maven.workspace.LocalWorkspace; import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils; +import io.quarkus.bootstrap.util.BootstrapUtils; import io.quarkus.bootstrap.util.IoUtils; import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactKey; @@ -52,8 +53,6 @@ public class BootstrapAppModelFactory { public static final String CREATOR_APP_TYPE = "creator.app.type"; public static final String CREATOR_APP_VERSION = "creator.app.version"; - private static final int CP_CACHE_FORMAT_ID = 2; - private static final Logger log = Logger.getLogger(BootstrapAppModelFactory.class); public static BootstrapAppModelFactory newInstance() { @@ -207,35 +206,26 @@ private BootstrapMavenContext createBootstrapMavenContext() throws AppModelResol } public CurationResult resolveAppModel() throws BootstrapException { - // gradle tests and dev encode the result on the class path - final String serializedModel; - if (test) { - serializedModel = System.getProperty(BootstrapConstants.SERIALIZED_TEST_APP_MODEL); - } else { - serializedModel = System.getProperty(BootstrapConstants.SERIALIZED_APP_MODEL); + CurationResult result = loadFromSystemProperty(); + if (result != null) { + return result; } - if (serializedModel != null) { - final Path p = Paths.get(serializedModel); - if (Files.exists(p)) { - try (InputStream existing = Files.newInputStream(p)) { - final ApplicationModel appModel = (ApplicationModel) new ObjectInputStream(existing).readObject(); - return new CurationResult(appModel); - } catch (IOException | ClassNotFoundException e) { - log.error("Failed to load serialized app mode", e); - } - IoUtils.recursiveDelete(p); - } else { - log.error("Failed to locate serialized application model at " + serializedModel); - } + result = createAppModelForJarOrNull(projectRoot); + if (result != null) { + return result; } - // Massive hack to dected zipped/jar - if (projectRoot != null - && (!Files.isDirectory(projectRoot) || projectRoot.getFileSystem().getClass().getName().contains("Zip"))) { - return createAppModelForJar(projectRoot); - } + return resolveAppModelForWorkspace(); + } + /** + * Resolves an application for a project in a workspace. + * + * @return application model + * @throws BootstrapException in case of a failure + */ + private CurationResult resolveAppModelForWorkspace() throws BootstrapException { ResolvedDependency appArtifact = this.appArtifact; try { LocalProject localProject = null; @@ -264,27 +254,10 @@ public CurationResult resolveAppModel() throws BootstrapException { cachedCpPath = resolveCachedCpPath(localProject); if (Files.exists(cachedCpPath) && workspace.getLastModified() < Files.getLastModifiedTime(cachedCpPath).toMillis()) { - try (DataInputStream reader = new DataInputStream(Files.newInputStream(cachedCpPath))) { - if (reader.readInt() == CP_CACHE_FORMAT_ID) { - if (reader.readInt() == workspace.getId()) { - ObjectInputStream in = new ObjectInputStream(reader); - ApplicationModel appModel = (ApplicationModel) in.readObject(); - - log.debugf("Loaded cached AppModel %s from %s", appModel, cachedCpPath); - for (ResolvedDependency d : appModel.getDependencies()) { - for (Path p : d.getResolvedPaths()) { - if (!Files.exists(p)) { - throw new IOException("Cached artifact does not exist: " + p); - } - } - } - return new CurationResult(appModel); - } else { - debug("Cached deployment classpath has expired for %s", appArtifact); - } - } else { - debug("Unsupported classpath cache format in %s for %s", cachedCpPath, - appArtifact); + try { + final ApplicationModel appModel = readAppModelWithWorkspaceId(cachedCpPath, workspace.getId()); + if (appModel != null) { + return new CurationResult(appModel); } } catch (IOException e) { log.warn("Failed to read deployment classpath cache from " + cachedCpPath + " for " @@ -296,12 +269,9 @@ public CurationResult resolveAppModel() throws BootstrapException { .resolveManagedModel(appArtifact, forcedDependencies, managingProject, reloadableModules)); if (cachedCpPath != null) { Files.createDirectories(cachedCpPath.getParent()); - try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(cachedCpPath))) { - out.writeInt(CP_CACHE_FORMAT_ID); - out.writeInt(workspace.getId()); - ObjectOutputStream obj = new ObjectOutputStream(out); - obj.writeObject(curationResult.getApplicationModel()); - } catch (Exception e) { + try { + writeAppModelWithWorkspaceId(curationResult.getApplicationModel(), workspace.getId(), cachedCpPath); + } catch (IOException e) { log.warn("Failed to write classpath cache", e); } } @@ -311,6 +281,37 @@ public CurationResult resolveAppModel() throws BootstrapException { } } + /** + * Attempts to load an application model from a file system path set as a value of a system property. + * In test mode the system property will be {@link BootstrapConstants#SERIALIZED_TEST_APP_MODEL}, otherwise + * it will be {@link BootstrapConstants#SERIALIZED_APP_MODEL}. + *

+ * If the property was not set, the method will return null. + *

+ * If the model could not deserialized, an error will be logged and null returned. + * + * @return deserialized application model or null + */ + private CurationResult loadFromSystemProperty() { + // gradle tests and dev encode the result on the class path + final String serializedModel = test ? System.getProperty(BootstrapConstants.SERIALIZED_TEST_APP_MODEL) + : System.getProperty(BootstrapConstants.SERIALIZED_APP_MODEL); + if (serializedModel != null) { + final Path p = Paths.get(serializedModel); + if (Files.exists(p)) { + try (InputStream existing = Files.newInputStream(p)) { + return new CurationResult((ApplicationModel) new ObjectInputStream(existing).readObject()); + } catch (IOException | ClassNotFoundException e) { + log.error("Failed to load serialized app mode", e); + } + IoUtils.recursiveDelete(p); + } else { + log.error("Failed to locate serialized application model at " + serializedModel); + } + } + return null; + } + private boolean isWorkspaceDiscoveryEnabled() { return localProjectsDiscovery == null ? projectRoot != null && (test || devMode) : localProjectsDiscovery; @@ -336,7 +337,20 @@ private LocalProject loadWorkspace() throws AppModelResolverException { return project; } - private CurationResult createAppModelForJar(Path appArtifactPath) { + /** + * Checks whether the project path is a JAR and if it is, creates an application model for it. + * If the project path is not a JAR, the method will return null. + * + * @param appArtifactPath application artifact path + * @return resolved application model or null + */ + private CurationResult createAppModelForJarOrNull(Path appArtifactPath) { + if (projectRoot == null + || Files.isDirectory(projectRoot) + // Massive hack to detect zipped/jar + || !projectRoot.getFileSystem().getClass().getName().contains("Zip")) { + return null; + } AppModelResolver modelResolver = getAppModelResolver(); final ApplicationModel appModel; ResolvedDependency appArtifact = this.appArtifact; @@ -355,15 +369,13 @@ private CurationResult createAppModelForJar(Path appArtifactPath) { } private Path resolveCachedCpPath(LocalProject project) { - final String filePrefix = devMode ? "dev-" : (test ? "test-" : null); - return project.getOutputDir().resolve(QUARKUS).resolve(BOOTSTRAP) - .resolve(filePrefix == null ? APP_MODEL_DAT : filePrefix + APP_MODEL_DAT); - } - - private static void debug(String msg, Object... args) { - if (log.isDebugEnabled()) { - log.debug(String.format(msg, args)); + if (devMode) { + return BootstrapUtils.resolveSerializedAppModelPath(project.getOutputDir()); + } + if (test) { + return BootstrapUtils.getSerializedTestAppModelPath(project.getOutputDir()); } + return project.getOutputDir().resolve(QUARKUS).resolve(BOOTSTRAP).resolve(APP_MODEL_DAT); } public BootstrapAppModelFactory setMavenArtifactResolver(MavenArtifactResolver mavenArtifactResolver) { diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java index ca502db7afa0a..31da5fc9c7c2d 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java @@ -162,7 +162,7 @@ static Path locateCurrentProjectPom(Path path, boolean required) throws Bootstra this.modelBuildingResult = modelBuildingResult; this.workspace = workspace; if (workspace != null) { - workspace.addProject(this, rawModel.getPomFile().lastModified()); + workspace.addProject(this); } } @@ -178,7 +178,7 @@ static Path locateCurrentProjectPom(Path path, boolean required) throws Bootstra version = rawVersionIsUnresolved ? ModelUtils.resolveVersion(rawVersion, rawModel) : rawVersion; if (workspace != null) { - workspace.addProject(this, rawModel.getPomFile().lastModified()); + workspace.addProject(this); if (rawVersionIsUnresolved && version != null) { workspace.setResolvedVersion(version); } @@ -187,6 +187,10 @@ static Path locateCurrentProjectPom(Path path, boolean required) throws Bootstra } } + protected long getPomLastModified() { + return rawModel.getPomFile().lastModified(); + } + public LocalProject getLocalParent() { if (parent != null) { return parent; diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalWorkspace.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalWorkspace.java index a570d9f80455f..660a7ccb920da 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalWorkspace.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalWorkspace.java @@ -5,6 +5,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -34,8 +35,8 @@ public class LocalWorkspace implements WorkspaceModelResolver, WorkspaceReader, private final WorkspaceRepository wsRepo = new WorkspaceRepository(); private ArtifactKey lastFindVersionsKey; private List lastFindVersions; - private long lastModified; - private int id = 1; + private volatile long lastModified = -1; + private volatile int id = -1; // value of the resolved version in case the raw version contains a property like ${revision} (see "Maven CI Friendly Versions") private String resolvedVersion; @@ -45,12 +46,8 @@ public class LocalWorkspace implements WorkspaceModelResolver, WorkspaceReader, private BootstrapMavenContext mvnCtx; private LocalProject currentProject; - protected void addProject(LocalProject project, long lastModified) { + protected void addProject(LocalProject project) { projects.put(project.getKey(), project); - if (lastModified > this.lastModified) { - this.lastModified = lastModified; - } - id = 31 * id + (int) (lastModified ^ (lastModified >>> 32)); } public LocalProject getProject(String groupId, String artifactId) { @@ -61,14 +58,43 @@ public LocalProject getProject(ArtifactKey key) { return projects.get(key); } + /** + * The latest last modified time of all the POMs in the workspace. + * + * @return the latest last modified time of all the POMs in the workspace + */ public long getLastModified() { + if (lastModified < 0) { + initLastModifiedAndHash(); + } return lastModified; } + /** + * This is essentially a hash code derived from each module's key. + * + * @return a hash code derived from each module's key + */ public int getId() { + if (id < 0) { + initLastModifiedAndHash(); + } return id; } + private void initLastModifiedAndHash() { + long lastModified = 0; + final int[] hashes = new int[projects.size()]; + int i = 0; + for (var project : projects.values()) { + lastModified = Math.max(project.getPomLastModified(), lastModified); + hashes[i++] = project.getKey().hashCode(); + } + Arrays.sort(hashes); + this.id = Arrays.hashCode(hashes); + this.lastModified = lastModified; + } + @Override public Model resolveRawModel(String groupId, String artifactId, String versionConstraint) throws UnresolvableModelException {