From a21e1b8f3adb947d1cb89ca4584b564eae9f18e8 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 4 Jul 2023 12:15:32 +0100 Subject: [PATCH 01/22] Support @WithUnnamedKey in documentation (cherry picked from commit f32919f14d0d97a2f29b02fadb2bcd185abdf652) --- .../annotation/processor/Constants.java | 1 + .../generate_doc/ConfigDocItemFinder.java | 61 +++++++++++++++---- .../processor/generate_doc/ConfigDocKey.java | 10 +++ .../generate_doc/MavenConfigDocBuilder.java | 1 + .../SummaryTableDocFormatter.java | 18 ++++-- 5 files changed, 75 insertions(+), 16 deletions(-) diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java b/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java index c664e4e5228bc..17895ba6a3180 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java @@ -66,6 +66,7 @@ final public class Constants { public static final String ANNOTATION_CONFIG_WITH_NAME = "io.smallrye.config.WithName"; public static final String ANNOTATION_CONFIG_WITH_PARENT_NAME = "io.smallrye.config.WithParentName"; public static final String ANNOTATION_CONFIG_WITH_DEFAULT = "io.smallrye.config.WithDefault"; + public static final String ANNOTATION_CONFIG_WITH_UNNAMED_KEY = "io.smallrye.config.WithUnnamedKey"; public static final Set SUPPORTED_ANNOTATIONS_TYPES = Set.of(ANNOTATION_BUILD_STEP, ANNOTATION_CONFIG_GROUP, ANNOTATION_CONFIG_ROOT, ANNOTATION_RECORDER); diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java index 7c42af3b24e17..207574ad1ca44 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java @@ -8,6 +8,7 @@ import static io.quarkus.annotation.processor.Constants.ANNOTATION_CONFIG_WITH_DEFAULT; import static io.quarkus.annotation.processor.Constants.ANNOTATION_CONFIG_WITH_NAME; import static io.quarkus.annotation.processor.Constants.ANNOTATION_CONFIG_WITH_PARENT_NAME; +import static io.quarkus.annotation.processor.Constants.ANNOTATION_CONFIG_WITH_UNNAMED_KEY; import static io.quarkus.annotation.processor.Constants.ANNOTATION_CONVERT_WITH; import static io.quarkus.annotation.processor.Constants.ANNOTATION_DEFAULT_CONVERTER; import static io.quarkus.annotation.processor.Constants.DOT; @@ -22,6 +23,8 @@ import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.hyphenate; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.hyphenateEnumValue; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.stringifyType; +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; import static javax.lang.model.element.Modifier.ABSTRACT; import java.io.IOException; @@ -171,6 +174,7 @@ private List recursivelyFindConfigItems(Element element, String r String hyphenatedFieldName = hyphenate(fieldName); String configDocMapKey = hyphenatedFieldName; + boolean unnamedMapKey = false; boolean isDeprecated = false; boolean generateDocumentation = true; ConfigDocSection configSection = new ConfigDocSection(); @@ -233,6 +237,8 @@ private List recursivelyFindConfigItems(Element element, String r defaultValueDoc = annotationMirror.getElementValues().values().iterator().next().getValue().toString(); } else if (annotationName.equals(ANNOTATION_CONFIG_WITH_DEFAULT)) { defaultValue = annotationMirror.getElementValues().values().iterator().next().getValue().toString(); + } else if (annotationName.equals(ANNOTATION_CONFIG_WITH_UNNAMED_KEY)) { + unnamedMapKey = true; } } @@ -254,7 +260,7 @@ private List recursivelyFindConfigItems(Element element, String r String type = getType(typeMirror); if (isConfigGroup(type)) { - List groupConfigItems = readConfigGroupItems(configPhase, rootName, name, type, + List groupConfigItems = readConfigGroupItems(configPhase, rootName, name, emptyList(), type, configSection, withinAMap, generateSeparateConfigGroupDocsFiles); DocGeneratorUtil.appendConfigItemsIntoExistingOnes(configDocItems, groupConfigItems); } else { @@ -277,9 +283,16 @@ private List recursivelyFindConfigItems(Element element, String r if (typeArguments.size() == 2) { type = getType(typeArguments.get(1)); if (isConfigGroup(type)) { - name += String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey); - List groupConfigItems = readConfigGroupItems(configPhase, rootName, name, type, - configSection, true, generateSeparateConfigGroupDocsFiles); + List additionalNames; + if (unnamedMapKey) { + additionalNames = List + .of(name + String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey)); + } else { + name += String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey); + additionalNames = emptyList(); + } + List groupConfigItems = readConfigGroupItems(configPhase, rootName, name, + additionalNames, type, configSection, true, generateSeparateConfigGroupDocsFiles); DocGeneratorUtil.appendConfigItemsIntoExistingOnes(configDocItems, groupConfigItems); continue; } else { @@ -305,7 +318,8 @@ private List recursivelyFindConfigItems(Element element, String r } configSection.setOptional(true); List groupConfigItems = readConfigGroupItems(configPhase, rootName, name, - typeInString, configSection, withinAMap, generateSeparateConfigGroupDocsFiles); + emptyList(), typeInString, configSection, withinAMap, + generateSeparateConfigGroupDocsFiles); DocGeneratorUtil.appendConfigItemsIntoExistingOnes(configDocItems, groupConfigItems); continue; } else if ((typeInString.startsWith(List.class.getName()) @@ -359,6 +373,7 @@ private List recursivelyFindConfigItems(Element element, String r } configDocKey.setKey(name); + configDocKey.setAdditionalKeys(emptyList()); configDocKey.setType(type); configDocKey.setList(list); configDocKey.setOptional(optional); @@ -513,8 +528,15 @@ private boolean isDurationType(TypeMirror realTypeMirror) { * configuration group if properly updated afterwards. * */ - private List readConfigGroupItems(ConfigPhase configPhase, String topLevelRootName, String parentName, - String configGroup, ConfigDocSection configSection, boolean withinAMap, boolean generateSeparateConfigGroupDocs) + private List readConfigGroupItems( + ConfigPhase configPhase, + String topLevelRootName, + String parentName, + List additionalNames, + String configGroup, + ConfigDocSection configSection, + boolean withinAMap, + boolean generateSeparateConfigGroupDocs) throws JsonProcessingException { configSection.setConfigGroupType(configGroup); @@ -538,8 +560,8 @@ private List readConfigGroupItems(ConfigPhase configPhase, String allConfigurationGroups.put(configGroup, OBJECT_MAPPER.writeValueAsString(groupConfigItems)); } - groupConfigItems = decorateGroupItems(groupConfigItems, configPhase, topLevelRootName, parentName, withinAMap, - generateSeparateConfigGroupDocs); + groupConfigItems = decorateGroupItems(groupConfigItems, configPhase, topLevelRootName, parentName, additionalNames, + withinAMap, generateSeparateConfigGroupDocs); // make sure that the config section is added if it is to be shown or when scanning parent configuration group // priory to scanning configuration roots. This is useful as we get indication of whether the config items are part @@ -565,8 +587,15 @@ private List readConfigGroupItems(ConfigPhase configPhase, String * The missing information come from configuration roots and these are config phase, top level root name and parent name (as * we are traversing down the tree) */ - private List decorateGroupItems(List groupConfigItems, ConfigPhase configPhase, - String topLevelRootName, String parentName, boolean withinAMap, boolean generateSeparateConfigGroupDocs) { + private List decorateGroupItems( + List groupConfigItems, + ConfigPhase configPhase, + String topLevelRootName, + String parentName, + List additionalNames, + boolean withinAMap, + boolean generateSeparateConfigGroupDocs) { + List decoratedItems = new ArrayList<>(); for (ConfigDocItem configDocItem : groupConfigItems) { if (configDocItem.isConfigKey()) { @@ -575,6 +604,15 @@ private List decorateGroupItems(List groupConfigIt configDocKey.setWithinAMap(configDocKey.isWithinAMap() || withinAMap); configDocKey.setWithinAConfigGroup(true); configDocKey.setTopLevelGrouping(topLevelRootName); + List additionalKeys = new ArrayList<>(); + for (String key : configDocKey.getAdditionalKeys()) { + additionalKeys.add(parentName + key); + for (String name : additionalNames) { + additionalKeys.add(name + key); + } + } + additionalKeys.addAll(additionalNames.stream().map(k -> k + configDocKey.getKey()).collect(toList())); + configDocKey.setAdditionalKeys(additionalKeys); configDocKey.setKey(parentName + configDocKey.getKey()); decoratedItems.add(configDocItem); } else { @@ -588,6 +626,7 @@ private List decorateGroupItems(List groupConfigIt configPhase, topLevelRootName, parentName, + additionalNames, section.isWithinAMap(), generateSeparateConfigGroupDocs); String configGroupType = section.getConfigGroupType(); diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocKey.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocKey.java index f961b4399b2ba..a853a0f92939e 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocKey.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocKey.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.Writer; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.regex.Matcher; @@ -11,6 +12,7 @@ final public class ConfigDocKey implements ConfigDocElement, Comparable { private String type; private String key; + private List additionalKeys = new ArrayList<>(); private String configDoc; private boolean withinAMap; private String defaultValue; @@ -61,6 +63,14 @@ public void setKey(String key) { this.key = key; } + public List getAdditionalKeys() { + return additionalKeys; + } + + public void setAdditionalKeys(final List additionalKeys) { + this.additionalKeys = additionalKeys; + } + public void setTopLevelGrouping(String topLevelGrouping) { this.topLevelGrouping = topLevelGrouping; } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/MavenConfigDocBuilder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/MavenConfigDocBuilder.java index e55a495fbc1a3..16d3c31a23205 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/MavenConfigDocBuilder.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/MavenConfigDocBuilder.java @@ -76,6 +76,7 @@ public void addParam(String type, String name, String defaultValue, boolean requ final ConfigDocKey configDocKey = new ConfigDocKey(); configDocKey.setType(type); configDocKey.setKey(name); + configDocKey.setAdditionalKeys(List.of(name)); configDocKey.setConfigPhase(ConfigPhase.RUN_TIME); configDocKey.setDefaultValue(defaultValue == null ? Constants.EMPTY : defaultValue); if (description != null && !description.isBlank()) { diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java index 654b484967c47..dd3756daa3bfd 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java @@ -114,11 +114,19 @@ public void format(Writer writer, ConfigDocKey configDocKey) throws IOException String configKeyAnchor = configDocKey.isPassThroughMap() ? getAnchor(key + Constants.DASH + configDocKey.getDocMapKey()) : getAnchor(key); String anchor = anchorPrefix + configKeyAnchor; - writer.append(String.format(TABLE_ROW_FORMAT, - configDocKey.getConfigPhase().getIllustration(), - anchor, - anchor, - key, + + StringBuilder keys = new StringBuilder(); + keys.append( + String.format("%s [[%s]]`link:#%s[%s]`\n\n", configDocKey.getConfigPhase().getIllustration(), anchor, anchor, + key)); + for (String additionalKey : configDocKey.getAdditionalKeys()) { + if (!additionalKey.equals(key)) { + keys.append(String.format("`link:#%s[%s]`\n\n", anchor, additionalKey)); + } + } + + writer.append(String.format("\n\na|%s\n[.description]\n--\n%s\n--%s|%s %s\n|%s\n", + keys, // make sure nobody inserts a table cell separator here doc.replace("|", "\\|"), // if ConfigDocKey is enum, cell style operator must support block elements From 7e8f0c1a2fb23e9a5e83ccc212adf00c614dc669 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 1 Aug 2023 14:44:22 +0200 Subject: [PATCH 02/22] ArC: fix deadlock when calling guarded methods on the same thread - fixes #35136 (cherry picked from commit e6c044e96f84851fe72ce0ad1f7911266c08c41f) --- .../io/quarkus/arc/impl/LockInterceptor.java | 72 ++++++++++++------- .../lock/LockInterceptorDeadlockTest.java | 53 ++++++++++++++ 2 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/lock/LockInterceptorDeadlockTest.java diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LockInterceptor.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LockInterceptor.java index 46f94cb31cbd6..f454f9feab243 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LockInterceptor.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LockInterceptor.java @@ -2,9 +2,7 @@ import static jakarta.interceptor.Interceptor.Priority.PLATFORM_BEFORE; -import java.lang.annotation.Annotation; -import java.util.Set; -import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import jakarta.annotation.Priority; @@ -21,10 +19,13 @@ @Priority(PLATFORM_BEFORE) public class LockInterceptor { - private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); + + // This lock is used exclusively to synchronize the block where we release all read locks and aquire the write lock + private final ReentrantLock rl = new ReentrantLock(); @AroundInvoke - Object lock(InvocationContext ctx) throws Exception { + Object lock(ArcInvocationContext ctx) throws Exception { Lock lock = getLock(ctx); switch (lock.value()) { case WRITE: @@ -33,27 +34,47 @@ Object lock(InvocationContext ctx) throws Exception { return readLock(lock, ctx); case NONE: return ctx.proceed(); + default: + throw new LockException("Unsupported @Lock type found on business method " + ctx.getMethod()); } - throw new LockException("Unsupported @Lock type found on business method " + ctx.getMethod()); } private Object writeLock(Lock lock, InvocationContext ctx) throws Exception { - boolean locked = false; long time = lock.time(); + int readHoldCount = rwl.getReadHoldCount(); + boolean locked = false; + try { - if (time > 0) { - locked = readWriteLock.writeLock().tryLock(time, lock.unit()); - if (!locked) { - throw new LockException("Write lock not acquired in " + lock.unit().toMillis(time) + " ms"); + rl.lock(); + try { + if (readHoldCount > 0) { + // Release all read locks hold by the current thread before acquiring the write lock + for (int i = 0; i < readHoldCount; i++) { + rwl.readLock().unlock(); + } } - } else { - readWriteLock.writeLock().lock(); - locked = true; + if (time > 0) { + locked = rwl.writeLock().tryLock(time, lock.unit()); + if (!locked) { + throw new LockException("Write lock not acquired in " + lock.unit().toMillis(time) + " ms"); + } + } else { + rwl.writeLock().lock(); + locked = true; + } + } finally { + rl.unlock(); } return ctx.proceed(); } finally { if (locked) { - readWriteLock.writeLock().unlock(); + if (readHoldCount > 0) { + // Re-aqcquire the read locks + for (int i = 0; i < readHoldCount; i++) { + rwl.readLock().lock(); + } + } + rwl.writeLock().unlock(); } } } @@ -63,32 +84,29 @@ private Object readLock(Lock lock, InvocationContext ctx) throws Exception { long time = lock.time(); try { if (time > 0) { - locked = readWriteLock.readLock().tryLock(time, lock.unit()); + locked = rwl.readLock().tryLock(time, lock.unit()); if (!locked) { throw new LockException("Read lock not acquired in " + lock.unit().toMillis(time) + " ms"); } } else { - readWriteLock.readLock().lock(); + rwl.readLock().lock(); locked = true; } return ctx.proceed(); } finally { if (locked) { - readWriteLock.readLock().unlock(); + rwl.readLock().unlock(); } } } - @SuppressWarnings("unchecked") - Lock getLock(InvocationContext ctx) { - Set bindings = (Set) ctx.getContextData().get(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); - for (Annotation annotation : bindings) { - if (annotation.annotationType().equals(Lock.class)) { - return (Lock) annotation; - } + Lock getLock(ArcInvocationContext ctx) { + Lock lock = ctx.findIterceptorBinding(Lock.class); + if (lock == null) { + // This should never happen + throw new LockException("@Lock binding not found on business method " + ctx.getMethod()); } - // This should never happen - throw new LockException("@Lock binding not found on business method " + ctx.getMethod()); + return lock; } } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/lock/LockInterceptorDeadlockTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/lock/LockInterceptorDeadlockTest.java new file mode 100644 index 0000000000000..809d8a57df5bd --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/lock/LockInterceptorDeadlockTest.java @@ -0,0 +1,53 @@ +package io.quarkus.arc.test.lock; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.Lock; +import io.quarkus.arc.Lock.Type; +import io.quarkus.arc.impl.LockInterceptor; +import io.quarkus.arc.test.ArcTestContainer; + +public class LockInterceptorDeadlockTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(SimpleApplicationScopedBean.class, Lock.class, + LockInterceptor.class); + + @Test + public void testApplicationScopedBean() throws Exception { + SimpleApplicationScopedBean bean = Arc.container().instance(SimpleApplicationScopedBean.class).get(); + assertTrue(bean.read()); + assertTrue(bean.nestedRead()); + assertTrue(bean.nestedWrite()); + } + + @ApplicationScoped + static class SimpleApplicationScopedBean { + + @Lock(Type.READ) + boolean read() { + return write(); + } + + @Lock(Type.READ) + boolean nestedRead() { + return read(); + } + + @Lock(Type.WRITE) + boolean write() { + return true; + } + + @Lock(Type.WRITE) + boolean nestedWrite() { + return nestedRead(); + } + } +} From 250d5b38b3bc6548f73a60607943b2bebbb19d18 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 2 Aug 2023 11:42:28 +0200 Subject: [PATCH 03/22] Properly use internal links to point to other guides Otherwise, our users won't have a consistent experience when browsing the maintenance version documentation. (cherry picked from commit 9967e0833b6a544ae5d2ed58464703c345929a93) --- .../main/asciidoc/azure-functions-http.adoc | 2 +- docs/src/main/asciidoc/azure-functions.adoc | 2 +- .../asciidoc/building-my-first-extension.adoc | 2 +- docs/src/main/asciidoc/cassandra.adoc | 7 +++---- docs/src/main/asciidoc/cli-tooling.adoc | 2 +- docs/src/main/asciidoc/datasource.adoc | 2 +- .../main/asciidoc/deploying-to-kubernetes.adoc | 4 ++-- .../main/asciidoc/deploying-to-openshift.adoc | 8 ++++---- .../main/asciidoc/extension-registry-user.adoc | 4 ++-- docs/src/main/asciidoc/kafka.adoc | 4 ++-- docs/src/main/asciidoc/maven-tooling.adoc | 2 +- ...security-basic-authentication-tutorial.adoc | 2 +- docs/src/main/asciidoc/security-jpa.adoc | 2 +- docs/src/main/asciidoc/stork-kubernetes.adoc | 4 ++-- docs/src/main/asciidoc/update-quarkus.adoc | 2 +- docs/src/main/asciidoc/vertx-reference.adoc | 18 +++++++++--------- 16 files changed, 33 insertions(+), 34 deletions(-) diff --git a/docs/src/main/asciidoc/azure-functions-http.adoc b/docs/src/main/asciidoc/azure-functions-http.adoc index 4496bd3014380..4316d91605134 100644 --- a/docs/src/main/asciidoc/azure-functions-http.adoc +++ b/docs/src/main/asciidoc/azure-functions-http.adoc @@ -160,7 +160,7 @@ The `quarkus-azure-functions-http` extension handles all the work to deploy to A Quarkus will use the Azure CLI in the background to authenticate and deploy to Azure. If you have multiple subscriptions associated with your account, you must set the `quarkus.azure-functions.subscription-id` property in your `application.properties` file to the subscription you want to use. -For other authentication mechanisms and deployment options see our config properties https://quarkus.io/guides/all-config[here]. +For other authentication mechanisms and deployment options see our config properties xref:all-config.adoc[here]. To run the deploy, after you build your project execute: diff --git a/docs/src/main/asciidoc/azure-functions.adoc b/docs/src/main/asciidoc/azure-functions.adoc index df4d9a81a7523..c58f6ece1dc8e 100644 --- a/docs/src/main/asciidoc/azure-functions.adoc +++ b/docs/src/main/asciidoc/azure-functions.adoc @@ -177,7 +177,7 @@ The `quarkus-azure-functions` extension handles all the work to deploy to Azure. Quarkus will use the Azure CLI in the background to authenticate and deploy to Azure. If you have multiple subscriptions associated with your account, you must set the `quarkus.azure-functions.subscription-id` property in your `application.properties` file to the subscription you want to use. -For other authentication mechanisms and deployment options see our config properties https://quarkus.io/guides/all-config[here]. +For other authentication mechanisms and deployment options see our config properties xref:all-config.adoc[here]. To run the deploy, after you build your project execute: diff --git a/docs/src/main/asciidoc/building-my-first-extension.adoc b/docs/src/main/asciidoc/building-my-first-extension.adoc index e64d7803ac132..3cc1d82011258 100644 --- a/docs/src/main/asciidoc/building-my-first-extension.adoc +++ b/docs/src/main/asciidoc/building-my-first-extension.adoc @@ -903,7 +903,7 @@ From an application developer perspective, a Quarkus platform is represented as link:https://github.com/quarkiverse[Quarkiverse Hub] is the GitHub organization that provides repository hosting (including build, CI and release publishing setup) for Quarkus extension projects contributed by the community. -In case you are wondering about creating a new Quarkus extension and adding it to the Quarkus ecosystem so that the Quarkus community can discover it using the Quarkus dev tools (including the https://quarkus.io/guides/cli-tooling[Quarkus CLI] and https://code.quarkus.io[code.quarkus.io]), the https://github.com/quarkiverse[Quarkiverse Hub] GitHub organization will be a good home for it. +In case you are wondering about creating a new Quarkus extension and adding it to the Quarkus ecosystem so that the Quarkus community can discover it using the Quarkus dev tools (including the xref:cli-tooling.adoc[Quarkus CLI] and https://code.quarkus.io[code.quarkus.io]), the https://github.com/quarkiverse[Quarkiverse Hub] GitHub organization will be a good home for it. You can get started by creating an link:https://github.com/quarkusio/quarkus/issues/new/choose[Extension Request] issue (check first if one wasn't already submitted link:https://github.com/quarkusio/quarkus/labels/kind%2Fextension-proposal[here]) and asking to lead it. diff --git a/docs/src/main/asciidoc/cassandra.adoc b/docs/src/main/asciidoc/cassandra.adoc index b6ebb0c1790e5..e68ada8b38153 100644 --- a/docs/src/main/asciidoc/cassandra.adoc +++ b/docs/src/main/asciidoc/cassandra.adoc @@ -789,8 +789,7 @@ until your application actually connects and hits the database for the first tim == Running in native mode -If you installed GraalVM, you can link:https://quarkus.io/guides/building-native-image[build a -native image] using: +If you installed GraalVM, you can xref:building-native-image.adoc[build a native image] using: [source,shell] ---- @@ -830,8 +829,8 @@ that needs to interact with the Cassandra database. Using lazy initialization speeds up your application startup time, and avoids startup failures if the Cassandra database is not available. However, it could also prove dangerous if your code is -fully non-blocking, for example if it uses https://quarkus.io/guides/reactive-routes[reactive -routes]. Indeed, the lazy initialization could accidentally happen on a thread that is not allowed +fully non-blocking, for example if it uses xref:reactive-routes.adoc[reactive routes]. +Indeed, the lazy initialization could accidentally happen on a thread that is not allowed to block, such as a Vert.x event loop thread. Therefore, setting `quarkus.cassandra.init.eager-init` to `false` and injecting `QuarkusCqlSession` should be avoided in these contexts. diff --git a/docs/src/main/asciidoc/cli-tooling.adoc b/docs/src/main/asciidoc/cli-tooling.adoc index aac26a5b05418..fa811bdf19c64 100644 --- a/docs/src/main/asciidoc/cli-tooling.adoc +++ b/docs/src/main/asciidoc/cli-tooling.adoc @@ -398,7 +398,7 @@ Both `quarkus create` and `quarkus extension list` allow you to explicitly speci 1. Specify a specific Platform Release BOM + -A https://quarkus.io/guides/platform#quarkus-platform-bom[Quarkus Platform release BOM] is identified by `groupId:artifactId:version` (GAV) coordinates. When specifying a platform release BOM, you may use empty segments to fall back to default values (shown with `quarkus create app --help`). If you specify only one segment (no `:`), it is assumed to be a version. +A xref:platform.adoc#quarkus-platform-bom[Quarkus Platform release BOM] is identified by `groupId:artifactId:version` (GAV) coordinates. When specifying a platform release BOM, you may use empty segments to fall back to default values (shown with `quarkus create app --help`). If you specify only one segment (no `:`), it is assumed to be a version. + `{quarkus-platform-groupid}` is the default `groupId`. Specifying `-P :quarkus-bom:` is equivalent to `-P {quarkus-platform-groupid}:quarkus-bom:{quarkus-version}`. Note that you need to specify the `groupId` to work with a snapshot, e.g. `-P io.quarkus::999-SNAPSHOT` is equivalent to `-P io.quarkus:quarkus-bom:999-SNAPSHOT`. + diff --git a/docs/src/main/asciidoc/datasource.adoc b/docs/src/main/asciidoc/datasource.adoc index 8c8f8f6887217..d5afd66cfb894 100644 --- a/docs/src/main/asciidoc/datasource.adoc +++ b/docs/src/main/asciidoc/datasource.adoc @@ -407,7 +407,7 @@ To exclude only a particular datasource from the health check, use: === Datasource metrics -If you are using the link:https://quarkus.io/guides/micrometer[`quarkus-micrometer`] or link:https://quarkus.io/guides/smallrye-metrics[`quarkus-smallrye-metrics`] extension, `quarkus-agroal` can contribute some datasource-related metrics to the metric registry. +If you are using the xref:micrometer.adoc[`quarkus-micrometer`] or xref:smallrye-metrics.adoc[`quarkus-smallrye-metrics`] extension, `quarkus-agroal` can contribute some datasource-related metrics to the metric registry. This can be activated by setting the `quarkus.datasource.metrics.enabled` property to `true`. For the exposed metrics to contain any actual values, a metric collection must be enabled internally by the Agroal mechanisms. diff --git a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc index 5895c885045e0..1e03c9dcd7b68 100644 --- a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc +++ b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc @@ -236,7 +236,7 @@ You can provide the arguments that will be used by the Kubernetes Job via the pr Finally, the Kubernetes job will be launched every time it is installed in Kubernetes. You can know more about how to run Kubernetes jobs in this https://kubernetes.io/docs/concepts/workloads/controllers/job/#running-an-example-job[link]. -You can configure the rest of the Kubernetes Job configuration using the properties under `quarkus.kubernetes.job.xxx` (see https://quarkus.io/guides/deploying-to-kubernetes#quarkus-kubernetes-kubernetes-config_quarkus.kubernetes.job.parallelism-parallelism[link]). +You can configure the rest of the Kubernetes Job configuration using the properties under `quarkus.kubernetes.job.xxx` (see xref:deploying-to-kubernetes.adoc#quarkus-kubernetes-kubernetes-config_quarkus.kubernetes.job.parallelism[link]). ==== Generating CronJob resources @@ -251,7 +251,7 @@ quarkus.kubernetes.cron-job.schedule=0 * * * * IMPORTANT: CronJob resources require the https://en.wikipedia.org/wiki/Cron[Cron] expression to specify when to launch the job via the property `quarkus.kubernetes.cron-job.schedule`. If not provide, the build will fail. -You can configure the rest of the Kubernetes CronJob configuration using the properties under `quarkus.kubernetes.cron-job.xxx` (see https://quarkus.io/guides/deploying-to-kubernetes#quarkus-kubernetes-kubernetes-config_quarkus.kubernetes.cron-job.parallelism-parallelism[link]). +You can configure the rest of the Kubernetes CronJob configuration using the properties under `quarkus.kubernetes.cron-job.xxx` (see xref:deploying-to-kubernetes.adoc#quarkus-kubernetes-kubernetes-config_quarkus.kubernetes.cron-job.parallelism[link]). === Namespace diff --git a/docs/src/main/asciidoc/deploying-to-openshift.adoc b/docs/src/main/asciidoc/deploying-to-openshift.adoc index cd15c8fddf5d6..1ff4619a3b958 100644 --- a/docs/src/main/asciidoc/deploying-to-openshift.adoc +++ b/docs/src/main/asciidoc/deploying-to-openshift.adoc @@ -91,7 +91,7 @@ include::{includes}/devtools/build.adoc[] TIP: If you want to test your application immediately then set the `quarkus.openshift.route.expose` config property to `true` to <>, e.g. add `-Dquarkus.openshift.route.expose=true` to the command above. [#re-deploy-with-service-binding] -NOTE: When using `DeploymentConfig` and https://quarkus.io/guides/deploying-to-kubernetes#service_binding[Service Binding], re-deploying might remove the configuration added by OpenShift to allow service discovery. A new container image build will trigger a refresh of the Quarkus app in OpenShift: `-Dquarkus.container-image.build=true` which might be enough in most situations. If you need to update the OpenShift resources, you need to delete the binding first to create it again after new deployment. +NOTE: When using `DeploymentConfig` and xref:deploying-to-kubernetes.adoc#service_binding[Service Binding], re-deploying might remove the configuration added by OpenShift to allow service discovery. A new container image build will trigger a refresh of the Quarkus app in OpenShift: `-Dquarkus.container-image.build=true` which might be enough in most situations. If you need to update the OpenShift resources, you need to delete the binding first to create it again after new deployment. This command will build your application locally, then trigger a container image build and finally apply the generated OpenShift resources automatically. The generated resources use OpenShift's `DeploymentConfig` that is configured to automatically trigger a redeployment when a change in the `ImageStream` is noticed. @@ -147,7 +147,7 @@ During the build you may find the `Caused by: javax.net.ssl.SSLHandshakeExceptio quarkus.kubernetes-client.trust-certs=true ---- -For more information, see link:https://quarkus.io/guides/deploying-to-kubernetes#client-connection-configuration[deploying to Kubernetes]. +For more information, see xref:deploying-to-kubernetes.adoc#client-connection-configuration[deploying to Kubernetes]. ==== Once the build is done we can create a new application from the relevant `ImageStream`. @@ -415,7 +415,7 @@ You can provide the arguments that will be used by the Kubernetes Job via the pr Finally, the Kubernetes job will be launched every time that is installed in OpenShift. You can know more about how to run Kubernetes jobs in this https://kubernetes.io/docs/concepts/workloads/controllers/job/#running-an-example-job[link]. -You can configure the rest of the Kubernetes Job configuration using the properties under `quarkus.openshift.job.xxx` (see https://quarkus.io/guides/deploying-to-openshift#quarkus-openshift-openshift-config_quarkus.openshift.job.parallelism[link]). +You can configure the rest of the Kubernetes Job configuration using the properties under `quarkus.openshift.job.xxx` (see xref:deploying-to-openshift#quarkus-openshift-openshift-config_quarkus.openshift.job.parallelism[link]). ===== Generating CronJob resources @@ -430,7 +430,7 @@ quarkus.openshift.cron-job.schedule=0 * * * * IMPORTANT: CronJob resources require the https://en.wikipedia.org/wiki/Cron[Cron] expression to specify when to launch the job via the property `quarkus.openshift.cron-job.schedule`. If not provide, the build will fail. -You can configure the rest of the Kubernetes CronJob configuration using the properties under `quarkus.openshift.cron-job.xxx` (see https://quarkus.io/guides/deploying-to-openshift#quarkus-openshift-openshift-config_quarkus.openshift.cron-job.parallelism[link]). +You can configure the rest of the Kubernetes CronJob configuration using the properties under `quarkus.openshift.cron-job.xxx` (see xref:deploying-to-openshift.adoc#quarkus-openshift-openshift-config_quarkus.openshift.cron-job.parallelism[link]). ==== Validation diff --git a/docs/src/main/asciidoc/extension-registry-user.adoc b/docs/src/main/asciidoc/extension-registry-user.adoc index 6768fa06b2218..2a2d88c2f5783 100644 --- a/docs/src/main/asciidoc/extension-registry-user.adoc +++ b/docs/src/main/asciidoc/extension-registry-user.adoc @@ -8,7 +8,7 @@ include::_attributes.adoc[] :categories: architecture :summary: Learn more about the notion of extension registry and how you can use your own. -The Quarkus dev tools, such as the https://quarkus.io/guides/cli-tooling[Quarkus CLI], the https://quarkus.io/guides/maven-tooling[Maven] and the https://quarkus.io/guides/gradle-tooling[Gradle] plugins, or https://code.quarkus.io[code.quarkus.io] can be used to list and search the Quarkus ecosystem for extensions that match a certain criteria. That includes the https://quarkus.io/guides/platform[Quarkus platform] extensions and various other extensions contributed by the community, many of which are hosted on the https://github.com/quarkiverse[Quarkiverse Hub]. +The Quarkus dev tools, such as the xref:cli-tooling.adoc[Quarkus CLI], the xref:maven-tooling.adoc[Maven] and the xref:gradle-tooling.adoc[Gradle] plugins, or https://code.quarkus.io[code.quarkus.io] can be used to list and search the Quarkus ecosystem for extensions that match a certain criteria. That includes the xref:platform.adoc[Quarkus platform] extensions and various other extensions contributed by the community, many of which are hosted on the https://github.com/quarkiverse[Quarkiverse Hub]. The information about all the available Quarkus extensions is provided to the dev tools by __Quarkus extension registries__. @@ -76,7 +76,7 @@ When the Quarkus dev tools are launched, a search for the registry client config === Configuring multiple registries -The <> is the default Quarkus community extension registry, but it is not meant to be always the only registry. Other organizations may find it useful to create their own Quarkus extension registries to provide their own https://quarkus.io/guides/platform[Quarkus platforms] and/or individual (non-platform) Quarkus extensions. Users wishing to enable custom Quarkus extension registries in their environment would need to add them to the registry client configuration file. +The <> is the default Quarkus community extension registry, but it is not meant to be always the only registry. Other organizations may find it useful to create their own Quarkus extension registries to provide their own xref:platform.adoc[Quarkus platforms] and/or individual (non-platform) Quarkus extensions. Users wishing to enable custom Quarkus extension registries in their environment would need to add them to the registry client configuration file. The registry client configuration file is a simple YAML file which contains a list of registries, for example: diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc index 9eaa7ee6b31f9..c641595183e58 100644 --- a/docs/src/main/asciidoc/kafka.adoc +++ b/docs/src/main/asciidoc/kafka.adoc @@ -782,7 +782,7 @@ Quarkus provides following state store implementations: - `quarkus-redis`: Uses the xref:redis-reference.adoc[`quarkus-redis-client`] extension to persist processing states. Jackson is used to serialize processing state in Json. For complex objects it is required to configure the `checkpoint.state-type` property with the class name of the object. -By default, the state store uses the default redis client, but if a link:https://quarkus.io/guides/redis-reference#default-and-named-clients[named client] is to be used, the client name can be specified using the `mp.messaging.incoming.[channel-name].checkpoint.quarkus-redis.client-name` property. +By default, the state store uses the default redis client, but if a xref:redis-reference.adoc#default-and-named-clients[named client] is to be used, the client name can be specified using the `mp.messaging.incoming.[channel-name].checkpoint.quarkus-redis.client-name` property. Processing states will be stored in Redis using the key naming scheme `[consumer-group-id]:[topic]:[partition]`. For example the configuration of the previous code would be the following: @@ -1294,7 +1294,7 @@ While a transaction is in progress, subsequent calls to the `withTransaction`, i Note that in Reactive Messaging, the execution of processing methods, is already serialized, unless `@Blocking(ordered = false)` is used. If `withTransaction` can be called concurrently, for example from a REST endpoint, it is recommended to limit the concurrency of the execution. -This can be done using the `@Bulkhead` annotation from link:https://quarkus.io/guides/smallrye-fault-tolerance[_Microprofile Fault Tolerance_]. +This can be done using the `@Bulkhead` annotation from xref:smallrye-fault-tolerance.adoc[_Microprofile Fault Tolerance_]. An example usage can be found in <>. ==== diff --git a/docs/src/main/asciidoc/maven-tooling.adoc b/docs/src/main/asciidoc/maven-tooling.adoc index 0bf7c0bb34b2e..cc758093937ed 100644 --- a/docs/src/main/asciidoc/maven-tooling.adoc +++ b/docs/src/main/asciidoc/maven-tooling.adoc @@ -59,7 +59,7 @@ If you are using the Maven command, the following table lists the attributes you | The artifact id of the target platform BOM. | `platformVersion` -| The version currently recommended by the https://quarkus.io/guides/extension-registry-user[Quarkus Extension Registry] +| The version currently recommended by the xref:extension-registry-user.adoc[Quarkus Extension Registry] | The version of the platform you want the project to use. It can also accept a version range, in which case the latest from the specified range will be used. | `javaVersion` diff --git a/docs/src/main/asciidoc/security-basic-authentication-tutorial.adoc b/docs/src/main/asciidoc/security-basic-authentication-tutorial.adoc index 91ce6c237fc59..06e11d4620c6b 100644 --- a/docs/src/main/asciidoc/security-basic-authentication-tutorial.adoc +++ b/docs/src/main/asciidoc/security-basic-authentication-tutorial.adoc @@ -334,7 +334,7 @@ As a result, the `security-jpa` defaults to using bcrypt-hashed passwords. == Test your application by using Dev Services for PostgreSQL -Complete the integration testing of your application in JVM and native modes by using xref:https://quarkus.io/guides/dev-services#databases[Dev Services for PostgreSQL] before you run your application in production mode. +Complete the integration testing of your application in JVM and native modes by using xref:dev-services.adoc#databases[Dev Services for PostgreSQL] before you run your application in production mode. To run your application in dev mode: diff --git a/docs/src/main/asciidoc/security-jpa.adoc b/docs/src/main/asciidoc/security-jpa.adoc index 68751f990c4fe..25f4d7195869c 100644 --- a/docs/src/main/asciidoc/security-jpa.adoc +++ b/docs/src/main/asciidoc/security-jpa.adoc @@ -17,7 +17,7 @@ Quarkus security offers a Jakarta Persistence integration to collect usernames, The following Jakarta Persistence entity specification demonstrates how users' information needs to be stored in a Jakarta Persistence entity and properly mapped so that Quarkus can retrieve this information from a database. -* The `@UserDefinition` annotation must be present on a Jakarta Persistence entity, regardless of whether link:https://quarkus.io/guides/hibernate-orm-panache[simplified Hibernate ORM with Panache] is used or not. +* The `@UserDefinition` annotation must be present on a Jakarta Persistence entity, regardless of whether xref:hibernate-orm-panache.adoc[simplified Hibernate ORM with Panache] is used or not. * The `@Username` and `@Password` field types are always `String`. diff --git a/docs/src/main/asciidoc/stork-kubernetes.adoc b/docs/src/main/asciidoc/stork-kubernetes.adoc index e07d379cbafa2..dce9a2509f978 100644 --- a/docs/src/main/asciidoc/stork-kubernetes.adoc +++ b/docs/src/main/asciidoc/stork-kubernetes.adoc @@ -108,7 +108,7 @@ implementation("io.quarkus:quarkus-container-image-jib") Let's start with the very beginning: the service we will discover, select and call. -The Red and Blue are two simple REST services serving an endpoint responding `Hello from Red!` and `Hello from Blue!` respectively. The code of both applications has been developed following the https://quarkus.io/guides/getting-started[Getting Started Guide]. +The Red and Blue are two simple REST services serving an endpoint responding `Hello from Red!` and `Hello from Blue!` respectively. The code of both applications has been developed following the xref:getting-started.adoc[Getting Started Guide]. As the goal of this guide is to show how to use Stork Kubernetes service discovery, we won't provide the specifics steps for the Red and Blue services. Their container images are already built and available in a public registry: @@ -409,7 +409,7 @@ The `quarkus.container-image.registry` contains the container registry to use. The `quarkus.kubernetes.ingress.expose` indicates that the service will be accessible from the outside of the cluster. The `quarkus.kubernetes.ingress.host` contains the url to access the service. We are using https://nip.io/[nip.io] wildcard for IP address mappings. -For a more customized configuration you can check the https://quarkus.io/guides/deploying-to-kubernetes[Deploying to Kubernetes guide] +For a more customized configuration you can check the xref:deploying-to-kubernetes.adoc[Deploying to Kubernetes guide] == Build and push the container image diff --git a/docs/src/main/asciidoc/update-quarkus.adoc b/docs/src/main/asciidoc/update-quarkus.adoc index b6f69f82fd3a4..3a09d03937e27 100644 --- a/docs/src/main/asciidoc/update-quarkus.adoc +++ b/docs/src/main/asciidoc/update-quarkus.adoc @@ -36,7 +36,7 @@ include::{includes}/prerequisites.adoc[] . Create a working branch for your project by using your version control system. -. To use the Quarkus CLI in the next step, link:https://quarkus.io/guides/cli-tooling#installing-the-cli[install the latest version of the Quarkus CLI]. +. To use the Quarkus CLI in the next step, xref:cli-tooling.adoc#installing-the-cli[install the latest version of the Quarkus CLI]. Confirm the version number using `quarkus -v`. . Go to the project directory and update the project to the latest stream: diff --git a/docs/src/main/asciidoc/vertx-reference.adoc b/docs/src/main/asciidoc/vertx-reference.adoc index f87565ce76105..6e0a63757ef36 100644 --- a/docs/src/main/asciidoc/vertx-reference.adoc +++ b/docs/src/main/asciidoc/vertx-reference.adoc @@ -82,7 +82,7 @@ Check the associated documentation to learn how to use them. |AMQP Client |`io.quarkus:quarkus-smallrye-reactive-messaging-amqp` (extension) -|https://quarkus.io/guides/amqp +|xref:amqp.adoc |Circuit Breaker |`io.smallrye.reactive:smallrye-mutiny-vertx-circuit-breaker` (external dependency) @@ -94,15 +94,15 @@ Check the associated documentation to learn how to use them. |DB2 Client |`io.quarkus:quarkus-reactive-db2-client` (extension) -|https://quarkus.io/guides/reactive-sql-clients +|xref:reactive-sql-clients.adoc |Kafka Client |`io.quarkus:quarkus-smallrye-reactive-messaging-kafka` (extension) -|https://quarkus.io/guides/kafka +|xref:kafka.adoc |Mail Client |`io.quarkus:quarkus-mailer` (extension) -|https://quarkus.io/guides/mailer +|xref:mailer.adoc |MQTT Client |`io.quarkus:quarkus-smallrye-reactive-messaging-mqtt` (extension) @@ -110,19 +110,19 @@ Check the associated documentation to learn how to use them. |MS SQL Client |`io.quarkus:quarkus-reactive-mssql-client` (extension) -|https://quarkus.io/guides/reactive-sql-clients +|xref:reactive-sql-clients.adoc |MySQL Client |`io.quarkus:quarkus-reactive-mysql-client` (extension) -|https://quarkus.io/guides/reactive-sql-clients +|xref:reactive-sql-clients.adoc |Oracle Client |`io.quarkus:quarkus-reactive-oracle-client` (extension) -|https://quarkus.io/guides/reactive-sql-clients +|xref:reactive-sql-clients.adoc |PostgreSQL Client |`io.quarkus:quarkus-reactive-pg-client` (extension) -|https://quarkus.io/guides/reactive-sql-clients +|xref:reactive-sql-clients.adoc |RabbitMQ Client |`io.smallrye.reactive:smallrye-mutiny-vertx-rabbitmq-client` (external dependency) @@ -130,7 +130,7 @@ Check the associated documentation to learn how to use them. |Redis Client |`io.quarkus:quarkus-redis-client` (extension) -|https://quarkus.io/guides/redis +|xref:redis.adoc |Web Client |`io.smallrye.reactive:smallrye-mutiny-vertx-web-client` (external dependency) From 1f1d6cfcb7181846cdf69ae5ef02bbbc081a73f9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 2 Aug 2023 14:14:19 +0200 Subject: [PATCH 04/22] Rename config classes for container image extensions to avoid conflicts (cherry picked from commit 9d3cc269c89d4262c8deec8386359e5403f48495) --- ...nfig.java => ContainerImageJibConfig.java} | 4 +- .../image/jib/deployment/JibProcessor.java | 36 ++++++++++-------- ...ava => ContainerImageOpenshiftConfig.java} | 4 +- .../deployment/OpenshiftProcessor.java | 38 +++++++++---------- .../openshift/deployment/OpenshiftUtils.java | 9 +++-- ...nfig.java => ContainerImageS2iConfig.java} | 4 +- .../image/s2i/deployment/S2iProcessor.java | 25 ++++++------ 7 files changed, 64 insertions(+), 56 deletions(-) rename extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/{JibConfig.java => ContainerImageJibConfig.java} (98%) rename extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/{OpenshiftConfig.java => ContainerImageOpenshiftConfig.java} (98%) rename extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/{S2iConfig.java => ContainerImageS2iConfig.java} (97%) diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/ContainerImageJibConfig.java similarity index 98% rename from extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java rename to extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/ContainerImageJibConfig.java index 106207774b677..cc3f2c1060d6a 100644 --- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java +++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/ContainerImageJibConfig.java @@ -9,8 +9,8 @@ import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; -@ConfigRoot(phase = ConfigPhase.BUILD_TIME) -public class JibConfig { +@ConfigRoot(name = "jib", phase = ConfigPhase.BUILD_TIME) +public class ContainerImageJibConfig { public static final String DEFAULT_WORKING_DIR = "/home/jboss"; /** diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java index afa60bb08ea23..e406fed36f2ea 100644 --- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java +++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java @@ -116,7 +116,7 @@ public AvailableContainerImageExtensionBuildItem availability() { // in order to make the AppCDS usable by the runtime JVM @BuildStep(onlyIf = JibBuild.class) public void appCDS(ContainerImageConfig containerImageConfig, CompiledJavaVersionBuildItem compiledJavaVersion, - JibConfig jibConfig, + ContainerImageJibConfig jibConfig, BuildProducer producer) { if (!containerImageConfig.isBuildExplicitlyEnabled() && !containerImageConfig.isPushExplicitlyEnabled()) { @@ -126,7 +126,7 @@ public void appCDS(ContainerImageConfig containerImageConfig, CompiledJavaVersio producer.produce(new AppCDSContainerImageBuildItem(determineBaseJvmImage(jibConfig, compiledJavaVersion))); } - private String determineBaseJvmImage(JibConfig jibConfig, CompiledJavaVersionBuildItem compiledJavaVersion) { + private String determineBaseJvmImage(ContainerImageJibConfig jibConfig, CompiledJavaVersionBuildItem compiledJavaVersion) { if (jibConfig.baseJvmImage.isPresent()) { return jibConfig.baseJvmImage.get(); } @@ -139,7 +139,7 @@ private String determineBaseJvmImage(JibConfig jibConfig, CompiledJavaVersionBui } @BuildStep(onlyIf = { IsNormal.class, JibBuild.class }, onlyIfNot = NativeBuild.class) - public void buildFromJar(ContainerImageConfig containerImageConfig, JibConfig jibConfig, + public void buildFromJar(ContainerImageConfig containerImageConfig, ContainerImageJibConfig jibConfig, PackageConfig packageConfig, ContainerImageInfoBuildItem containerImage, JarBuildItem sourceJar, @@ -192,7 +192,7 @@ public void buildFromJar(ContainerImageConfig containerImageConfig, JibConfig ji } @BuildStep(onlyIf = { IsNormal.class, JibBuild.class, NativeBuild.class }) - public void buildFromNative(ContainerImageConfig containerImageConfig, JibConfig jibConfig, + public void buildFromNative(ContainerImageConfig containerImageConfig, ContainerImageJibConfig jibConfig, ContainerImageInfoBuildItem containerImage, NativeImageBuildItem nativeImage, OutputTargetBuildItem outputTarget, @@ -232,7 +232,8 @@ public void buildFromNative(ContainerImageConfig containerImageConfig, JibConfig } private JibContainer containerize(ContainerImageConfig containerImageConfig, - JibConfig jibConfig, ContainerImageInfoBuildItem containerImage, JibContainerBuilder jibContainerBuilder, + ContainerImageJibConfig jibConfig, ContainerImageInfoBuildItem containerImage, + JibContainerBuilder jibContainerBuilder, boolean pushRequested) { Containerizer containerizer = createContainerizer(containerImageConfig, jibConfig, containerImage, pushRequested); @@ -266,7 +267,7 @@ private JibContainer containerize(ContainerImageConfig containerImageConfig, } private Containerizer createContainerizer(ContainerImageConfig containerImageConfig, - JibConfig jibConfig, ContainerImageInfoBuildItem containerImageInfo, + ContainerImageJibConfig jibConfig, ContainerImageInfoBuildItem containerImageInfo, boolean pushRequested) { Containerizer containerizer; ImageReference imageReference = ImageReference.of(containerImageInfo.getRegistry().orElse(null), @@ -310,7 +311,8 @@ private Containerizer createContainerizer(ContainerImageConfig containerImageCon return containerizer; } - private void writeOutputFiles(JibContainer jibContainer, JibConfig jibConfig, OutputTargetBuildItem outputTarget) { + private void writeOutputFiles(JibContainer jibContainer, ContainerImageJibConfig jibConfig, + OutputTargetBuildItem outputTarget) { doWriteOutputFile(outputTarget, Paths.get(jibConfig.imageDigestFile), jibContainer.getDigest().toString()); doWriteOutputFile(outputTarget, Paths.get(jibConfig.imageIdFile), jibContainer.getImageId().toString()); } @@ -374,7 +376,7 @@ private Logger.Level toJBossLoggingLevel(LogEvent.Level level) { *
  • app
  • * */ - private JibContainerBuilder createContainerBuilderFromFastJar(String baseJvmImage, JibConfig jibConfig, + private JibContainerBuilder createContainerBuilderFromFastJar(String baseJvmImage, ContainerImageJibConfig jibConfig, ContainerImageConfig containerImageConfig, JarBuildItem sourceJarBuildItem, CurateOutcomeBuildItem curateOutcome, List containerImageLabels, @@ -534,7 +536,7 @@ private JibContainerBuilder createContainerBuilderFromFastJar(String baseJvmImag workDirInContainer, "fast-jar-quarkus-app", isMutableJar, modificationTime); addLayer(jibContainerBuilder, Collections.singletonList(componentsPath.resolve(JarResultBuildStep.QUARKUS)), workDirInContainer, "fast-jar-quarkus", isMutableJar, modificationTime); - if (JibConfig.DEFAULT_WORKING_DIR.equals(jibConfig.workingDirectory)) { + if (ContainerImageJibConfig.DEFAULT_WORKING_DIR.equals(jibConfig.workingDirectory)) { // this layer ensures that the working directory is writeable // see https://github.com/GoogleContainerTools/jib/issues/1270 // TODO: is this needed for all working directories? @@ -615,7 +617,8 @@ private void mayInheritEntrypoint(JibContainerBuilder jibContainerBuilder, List< } } - private List determineEffectiveJvmArguments(JibConfig jibConfig, Optional appCDSResult) { + private List determineEffectiveJvmArguments(ContainerImageJibConfig jibConfig, + Optional appCDSResult) { List effectiveJvmArguments = new ArrayList<>(jibConfig.jvmArguments); jibConfig.jvmAdditionalArguments.ifPresent(effectiveJvmArguments::addAll); if (appCDSResult.isPresent()) { @@ -633,15 +636,15 @@ private List determineEffectiveJvmArguments(JibConfig jibConfig, Optiona return effectiveJvmArguments; } - private void setUser(JibConfig jibConfig, JibContainerBuilder jibContainerBuilder) { + private void setUser(ContainerImageJibConfig jibConfig, JibContainerBuilder jibContainerBuilder) { jibConfig.user.ifPresent(jibContainerBuilder::setUser); } - private void setPlatforms(JibConfig jibConfig, JibContainerBuilder jibContainerBuilder) { + private void setPlatforms(ContainerImageJibConfig jibConfig, JibContainerBuilder jibContainerBuilder) { jibConfig.platforms.map(PlatformHelper::parse).ifPresent(jibContainerBuilder::setPlatforms); } - private JibContainerBuilder createContainerBuilderFromLegacyJar(String baseJvmImage, JibConfig jibConfig, + private JibContainerBuilder createContainerBuilderFromLegacyJar(String baseJvmImage, ContainerImageJibConfig jibConfig, ContainerImageConfig containerImageConfig, JarBuildItem sourceJarBuildItem, OutputTargetBuildItem outputTargetBuildItem, @@ -696,7 +699,8 @@ private JibContainerBuilder createContainerBuilderFromLegacyJar(String baseJvmIm } } - private JibContainerBuilder createContainerBuilderFromNative(JibConfig jibConfig, ContainerImageConfig containerImageConfig, + private JibContainerBuilder createContainerBuilderFromNative(ContainerImageJibConfig jibConfig, + ContainerImageConfig containerImageConfig, NativeImageBuildItem nativeImageBuildItem, List containerImageLabels) { List entrypoint; @@ -737,7 +741,7 @@ private JibContainerBuilder createContainerBuilderFromNative(JibConfig jibConfig } } - private Map getEnvironmentVariables(JibConfig jibConfig) { + private Map getEnvironmentVariables(ContainerImageJibConfig jibConfig) { Map original = jibConfig.environmentVariables; if (original.isEmpty()) { return original; @@ -788,7 +792,7 @@ private void handleExtraFiles(OutputTargetBuildItem outputTarget, JibContainerBu } } - private Map allLabels(JibConfig jibConfig, ContainerImageConfig containerImageConfig, + private Map allLabels(ContainerImageJibConfig jibConfig, ContainerImageConfig containerImageConfig, List containerImageLabels) { if (containerImageLabels.isEmpty() && containerImageConfig.labels.isEmpty()) { return Collections.emptyMap(); diff --git a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftConfig.java b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/ContainerImageOpenshiftConfig.java similarity index 98% rename from extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftConfig.java rename to extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/ContainerImageOpenshiftConfig.java index 163197535c721..29e7c70056e7f 100644 --- a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftConfig.java +++ b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/ContainerImageOpenshiftConfig.java @@ -12,8 +12,8 @@ import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; -@ConfigRoot(phase = ConfigPhase.BUILD_TIME) -public class OpenshiftConfig { +@ConfigRoot(name = "openshift", phase = ConfigPhase.BUILD_TIME) +public class ContainerImageOpenshiftConfig { public static final String DEFAULT_BASE_JVM_JDK11_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11:1.16"; public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.16"; diff --git a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftProcessor.java b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftProcessor.java index 1bca77ab95984..4a69364ac5951 100644 --- a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftProcessor.java +++ b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftProcessor.java @@ -91,11 +91,11 @@ public AvailableContainerImageExtensionBuildItem availability() { } @BuildStep(onlyIf = { OpenshiftBuild.class }, onlyIfNot = NativeBuild.class) - public void openshiftPrepareJvmDockerBuild(OpenshiftConfig openshiftConfig, + public void openshiftPrepareJvmDockerBuild(ContainerImageOpenshiftConfig openshiftConfig, S2iConfig s2iConfig, OutputTargetBuildItem out, BuildProducer decorator) { - OpenshiftConfig config = mergeConfig(openshiftConfig, s2iConfig); + ContainerImageOpenshiftConfig config = mergeConfig(openshiftConfig, s2iConfig); if (config.buildStrategy == BuildStrategy.DOCKER) { decorator.produce(new DecoratorBuildItem(new ApplyDockerfileToBuildConfigDecorator(null, findMainSourcesRoot(out.getOutputDirectory()).getValue().resolve(openshiftConfig.jvmDockerfile)))); @@ -106,11 +106,11 @@ public void openshiftPrepareJvmDockerBuild(OpenshiftConfig openshiftConfig, } @BuildStep(onlyIf = { OpenshiftBuild.class, NativeBuild.class }) - public void openshiftPrepareNativeDockerBuild(OpenshiftConfig openshiftConfig, + public void openshiftPrepareNativeDockerBuild(ContainerImageOpenshiftConfig openshiftConfig, S2iConfig s2iConfig, OutputTargetBuildItem out, BuildProducer decorator) { - OpenshiftConfig config = mergeConfig(openshiftConfig, s2iConfig); + ContainerImageOpenshiftConfig config = mergeConfig(openshiftConfig, s2iConfig); if (config.buildStrategy == BuildStrategy.DOCKER) { decorator.produce(new DecoratorBuildItem(new ApplyDockerfileToBuildConfigDecorator(null, findMainSourcesRoot(out.getOutputDirectory()).getValue().resolve(openshiftConfig.nativeDockerfile)))); @@ -121,7 +121,7 @@ public void openshiftPrepareNativeDockerBuild(OpenshiftConfig openshiftConfig, } @BuildStep(onlyIf = { IsNormalNotRemoteDev.class, OpenshiftBuild.class }, onlyIfNot = NativeBuild.class) - public void openshiftRequirementsJvm(OpenshiftConfig openshiftConfig, + public void openshiftRequirementsJvm(ContainerImageOpenshiftConfig openshiftConfig, S2iConfig s2iConfig, CurateOutcomeBuildItem curateOutcomeBuildItem, OutputTargetBuildItem out, @@ -133,11 +133,11 @@ public void openshiftRequirementsJvm(OpenshiftConfig openshiftConfig, BuildProducer builderImageProducer, BuildProducer commandProducer) { - OpenshiftConfig config = mergeConfig(openshiftConfig, s2iConfig); + ContainerImageOpenshiftConfig config = mergeConfig(openshiftConfig, s2iConfig); String outputJarFileName = jarBuildItem.getPath().getFileName().toString(); String jarFileName = config.jarFileName.orElse(outputJarFileName); String baseJvmImage = config.baseJvmImage - .orElse(OpenshiftConfig.getDefaultJvmImage(compiledJavaVersion.getJavaVersion())); + .orElse(ContainerImageOpenshiftConfig.getDefaultJvmImage(compiledJavaVersion.getJavaVersion())); boolean hasCustomJarPath = config.jarFileName.isPresent() || config.jarDirectory.isPresent(); boolean hasCustomJvmArguments = config.jvmArguments.isPresent(); @@ -176,7 +176,7 @@ public void openshiftRequirementsJvm(OpenshiftConfig openshiftConfig, } @BuildStep(onlyIf = { IsNormalNotRemoteDev.class, OpenshiftBuild.class, NativeBuild.class }) - public void openshiftRequirementsNative(OpenshiftConfig openshiftConfig, + public void openshiftRequirementsNative(ContainerImageOpenshiftConfig openshiftConfig, S2iConfig s2iConfig, CurateOutcomeBuildItem curateOutcomeBuildItem, OutputTargetBuildItem out, @@ -186,8 +186,8 @@ public void openshiftRequirementsNative(OpenshiftConfig openshiftConfig, BuildProducer builderImageProducer, BuildProducer commandProducer) { - OpenshiftConfig config = mergeConfig(openshiftConfig, s2iConfig); - boolean usingDefaultBuilder = ImageUtil.getRepository(OpenshiftConfig.DEFAULT_BASE_NATIVE_IMAGE) + ContainerImageOpenshiftConfig config = mergeConfig(openshiftConfig, s2iConfig); + boolean usingDefaultBuilder = ImageUtil.getRepository(ContainerImageOpenshiftConfig.DEFAULT_BASE_NATIVE_IMAGE) .equals(ImageUtil.getRepository(config.baseNativeImage)); String outputNativeBinaryFileName = nativeImage.getPath().getFileName().toString(); @@ -199,7 +199,7 @@ public void openshiftRequirementsNative(OpenshiftConfig openshiftConfig, //The default openshift builder for native builds, renames the native binary. //To make things easier for the user, we need to handle it. if (usingDefaultBuilder && !config.nativeBinaryFileName.isPresent()) { - nativeBinaryFileName = OpenshiftConfig.DEFAULT_NATIVE_TARGET_FILENAME; + nativeBinaryFileName = ContainerImageOpenshiftConfig.DEFAULT_NATIVE_TARGET_FILENAME; } else { nativeBinaryFileName = config.nativeBinaryFileName.orElse(outputNativeBinaryFileName); } @@ -234,7 +234,7 @@ public void openshiftRequirementsNative(OpenshiftConfig openshiftConfig, } @BuildStep(onlyIf = { IsNormalNotRemoteDev.class, OpenshiftBuild.class }, onlyIfNot = NativeBuild.class) - public void openshiftBuildFromJar(OpenshiftConfig openshiftConfig, + public void openshiftBuildFromJar(ContainerImageOpenshiftConfig openshiftConfig, S2iConfig s2iConfig, ContainerImageConfig containerImageConfig, KubernetesClientBuildItem kubernetesClientBuilder, @@ -248,7 +248,7 @@ public void openshiftBuildFromJar(OpenshiftConfig openshiftConfig, // used to ensure that the jar has been built JarBuildItem jar) { - OpenshiftConfig config = mergeConfig(openshiftConfig, s2iConfig); + ContainerImageOpenshiftConfig config = mergeConfig(openshiftConfig, s2iConfig); if (containerImageConfig.isBuildExplicitlyDisabled()) { return; } @@ -306,7 +306,7 @@ private String getContextRoot(String outputDirName, boolean isFastJar, BuildStra } @BuildStep(onlyIf = { IsNormalNotRemoteDev.class, OpenshiftBuild.class, NativeBuild.class }) - public void openshiftBuildFromNative(OpenshiftConfig openshiftConfig, S2iConfig s2iConfig, + public void openshiftBuildFromNative(ContainerImageOpenshiftConfig openshiftConfig, S2iConfig s2iConfig, ContainerImageConfig containerImageConfig, KubernetesClientBuildItem kubernetesClientBuilder, ContainerImageInfoBuildItem containerImage, @@ -318,7 +318,7 @@ public void openshiftBuildFromNative(OpenshiftConfig openshiftConfig, S2iConfig BuildProducer containerImageBuilder, NativeImageBuildItem nativeImage) { - OpenshiftConfig config = mergeConfig(openshiftConfig, s2iConfig); + ContainerImageOpenshiftConfig config = mergeConfig(openshiftConfig, s2iConfig); if (containerImageConfig.isBuildExplicitlyDisabled()) { return; @@ -359,7 +359,7 @@ public void openshiftBuildFromNative(OpenshiftConfig openshiftConfig, S2iConfig public static void createContainerImage(KubernetesClientBuilder kubernetesClientBuilder, GeneratedFileSystemResourceBuildItem openshiftManifests, - OpenshiftConfig openshiftConfig, + ContainerImageOpenshiftConfig openshiftConfig, String base, Path output, Path... additional) { @@ -423,7 +423,7 @@ private static void applyOpenshiftResources(OpenShiftClient client, List buildResources, File binaryFile, - OpenshiftConfig openshiftConfig, KubernetesClientBuilder kubernetesClientBuilder) { + ContainerImageOpenshiftConfig openshiftConfig, KubernetesClientBuilder kubernetesClientBuilder) { distinct(buildResources).stream().filter(i -> i instanceof BuildConfig).map(i -> (BuildConfig) i) .forEach(bc -> { Build build = startOpenshiftBuild(bc, binaryFile, openshiftConfig, kubernetesClientBuilder); @@ -441,7 +441,7 @@ private static void openshiftBuild(List buildResources, File binary * @param kubernetesClientBuilder The kubernetes client builder */ private static Build startOpenshiftBuild(BuildConfig buildConfig, File binaryFile, - OpenshiftConfig openshiftConfig, KubernetesClientBuilder kubernetesClientBuilder) { + ContainerImageOpenshiftConfig openshiftConfig, KubernetesClientBuilder kubernetesClientBuilder) { try (KubernetesClient kubernetesClient = kubernetesClientBuilder.build()) { OpenShiftClient client = toOpenshiftClient(kubernetesClient); try { @@ -462,7 +462,7 @@ private static Build startOpenshiftBuild(BuildConfig buildConfig, File binaryFil } } - private static void waitForOpenshiftBuild(Build build, OpenshiftConfig openshiftConfig, + private static void waitForOpenshiftBuild(Build build, ContainerImageOpenshiftConfig openshiftConfig, KubernetesClientBuilder kubernetesClientBuilder) { while (isNew(build) || isPending(build) || isRunning(build)) { diff --git a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftUtils.java b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftUtils.java index e2bdb427a915c..43341ba0cedaa 100644 --- a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftUtils.java +++ b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftUtils.java @@ -87,14 +87,15 @@ public void visit(SourceBuildStrategyFluent strategy) { } /** - * Merges {@link OpenshiftConfig} with {@link S2iConfig} prioritizing in the former. + * Merges {@link ContainerImageOpenshiftConfig} with {@link S2iConfig} prioritizing in the former. * * @param openshiftConfig the Openshift config * @param s2iConfig the s2i config - * @return an instance of {@link OpenshiftConfig} with the merged configuration. + * @return an instance of {@link ContainerImageOpenshiftConfig} with the merged configuration. */ - public static OpenshiftConfig mergeConfig(OpenshiftConfig openshiftConfig, S2iConfig s2iConfig) { - OpenshiftConfig result = openshiftConfig != null ? openshiftConfig : new OpenshiftConfig(); + public static ContainerImageOpenshiftConfig mergeConfig(ContainerImageOpenshiftConfig openshiftConfig, + S2iConfig s2iConfig) { + ContainerImageOpenshiftConfig result = openshiftConfig != null ? openshiftConfig : new ContainerImageOpenshiftConfig(); if (s2iConfig == null) { return result; } diff --git a/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/S2iConfig.java b/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/ContainerImageS2iConfig.java similarity index 97% rename from extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/S2iConfig.java rename to extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/ContainerImageS2iConfig.java index 42ec6eec02eb1..361aba9082290 100644 --- a/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/S2iConfig.java +++ b/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/ContainerImageS2iConfig.java @@ -10,8 +10,8 @@ import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; -@ConfigRoot(phase = ConfigPhase.BUILD_TIME) -public class S2iConfig { +@ConfigRoot(name = "s2i", phase = ConfigPhase.BUILD_TIME) +public class ContainerImageS2iConfig { public static final String DEFAULT_BASE_JVM_JDK11_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11"; public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17"; diff --git a/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/S2iProcessor.java b/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/S2iProcessor.java index 8dcb512328c26..8d93544dfc221 100644 --- a/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/S2iProcessor.java +++ b/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/S2iProcessor.java @@ -82,7 +82,7 @@ public AvailableContainerImageExtensionBuildItem availability() { } @BuildStep(onlyIf = { IsNormalNotRemoteDev.class, S2iBuild.class }, onlyIfNot = NativeBuild.class) - public void s2iRequirementsJvm(S2iConfig s2iConfig, + public void s2iRequirementsJvm(ContainerImageS2iConfig s2iConfig, CurateOutcomeBuildItem curateOutcomeBuildItem, OutputTargetBuildItem out, PackageConfig packageConfig, @@ -104,7 +104,8 @@ public void s2iRequirementsJvm(S2iConfig s2iConfig, String jarFileName = s2iConfig.jarFileName.orElse(outputJarFileName); String jarDirectory = s2iConfig.jarDirectory; String pathToJar = concatUnixPaths(jarDirectory, jarFileName); - String baseJvmImage = s2iConfig.baseJvmImage.orElse(S2iConfig.getDefaultJvmImage(compiledJavaVersion.getJavaVersion())); + String baseJvmImage = s2iConfig.baseJvmImage + .orElse(ContainerImageS2iConfig.getDefaultJvmImage(compiledJavaVersion.getJavaVersion())); builderImageProducer.produce(new BaseImageInfoBuildItem(baseJvmImage)); Optional baseImage = S2iBaseJavaImage.findMatching(baseJvmImage); @@ -128,7 +129,7 @@ public void s2iRequirementsJvm(S2iConfig s2iConfig, } @BuildStep(onlyIf = { IsNormalNotRemoteDev.class, S2iBuild.class, NativeBuild.class }) - public void s2iRequirementsNative(S2iConfig s2iConfig, + public void s2iRequirementsNative(ContainerImageS2iConfig s2iConfig, CurateOutcomeBuildItem curateOutcomeBuildItem, OutputTargetBuildItem out, PackageConfig packageConfig, @@ -137,7 +138,7 @@ public void s2iRequirementsNative(S2iConfig s2iConfig, BuildProducer builderImageProducer, BuildProducer commandProducer) { - boolean usingDefaultBuilder = ImageUtil.getRepository(S2iConfig.DEFAULT_BASE_NATIVE_IMAGE) + boolean usingDefaultBuilder = ImageUtil.getRepository(ContainerImageS2iConfig.DEFAULT_BASE_NATIVE_IMAGE) .equals(ImageUtil.getRepository(s2iConfig.baseNativeImage)); String outputNativeBinaryFileName = nativeImage.getPath().getFileName().toString(); @@ -146,7 +147,7 @@ public void s2iRequirementsNative(S2iConfig s2iConfig, //The default s2i builder for native builds, renames the native binary. //To make things easier for the user, we need to handle it. if (usingDefaultBuilder && !s2iConfig.nativeBinaryFileName.isPresent()) { - nativeBinaryFileName = S2iConfig.DEFAULT_NATIVE_TARGET_FILENAME; + nativeBinaryFileName = ContainerImageS2iConfig.DEFAULT_NATIVE_TARGET_FILENAME; } else { nativeBinaryFileName = s2iConfig.nativeBinaryFileName.orElse(outputNativeBinaryFileName); } @@ -170,7 +171,7 @@ public void s2iRequirementsNative(S2iConfig s2iConfig, } @BuildStep(onlyIf = { IsNormalNotRemoteDev.class, S2iBuild.class }, onlyIfNot = NativeBuild.class) - public void s2iBuildFromJar(S2iConfig s2iConfig, ContainerImageConfig containerImageConfig, + public void s2iBuildFromJar(ContainerImageS2iConfig s2iConfig, ContainerImageConfig containerImageConfig, KubernetesClientBuildItem kubernetesClientBuilder, ContainerImageInfoBuildItem containerImage, ArchiveRootBuildItem archiveRoot, OutputTargetBuildItem out, PackageConfig packageConfig, @@ -214,7 +215,7 @@ public void s2iBuildFromJar(S2iConfig s2iConfig, ContainerImageConfig containerI } @BuildStep(onlyIf = { IsNormalNotRemoteDev.class, S2iBuild.class, NativeBuild.class }) - public void s2iBuildFromNative(S2iConfig s2iConfig, ContainerImageConfig containerImageConfig, + public void s2iBuildFromNative(ContainerImageS2iConfig s2iConfig, ContainerImageConfig containerImageConfig, KubernetesClientBuildItem kubernetesClientBuilder, ContainerImageInfoBuildItem containerImage, ArchiveRootBuildItem archiveRoot, OutputTargetBuildItem out, PackageConfig packageConfig, @@ -259,7 +260,7 @@ public void s2iBuildFromNative(S2iConfig s2iConfig, ContainerImageConfig contain public static void createContainerImage(KubernetesClient kubernetesClient, GeneratedFileSystemResourceBuildItem openshiftManifests, - S2iConfig s2iConfig, + ContainerImageS2iConfig s2iConfig, Path output, Path... additional) { @@ -331,7 +332,7 @@ private static void applyS2iResources(OpenShiftClient client, List } private static void s2iBuild(OpenShiftClient client, List buildResources, File binaryFile, - S2iConfig s2iConfig) { + ContainerImageS2iConfig s2iConfig) { distinct(buildResources).stream().filter(i -> i instanceof BuildConfig).map(i -> (BuildConfig) i) .forEach(bc -> s2iBuild(client, bc, binaryFile, s2iConfig)); } @@ -345,7 +346,8 @@ private static void s2iBuild(OpenShiftClient client, List buildReso * @param binaryFile The binary file * @param s2iConfig The s2i configuration */ - private static void s2iBuild(OpenShiftClient client, BuildConfig buildConfig, File binaryFile, S2iConfig s2iConfig) { + private static void s2iBuild(OpenShiftClient client, BuildConfig buildConfig, File binaryFile, + ContainerImageS2iConfig s2iConfig) { Build build; try { build = client.buildConfigs().withName(buildConfig.getMetadata().getName()) @@ -375,7 +377,8 @@ private static void s2iBuild(OpenShiftClient client, BuildConfig buildConfig, Fi } } - private static void waitForBuildComplete(OpenShiftClient client, S2iConfig s2iConfig, String buildName, Closeable watch) { + private static void waitForBuildComplete(OpenShiftClient client, ContainerImageS2iConfig s2iConfig, String buildName, + Closeable watch) { CountDownLatch latch = new CountDownLatch(1); ExecutorService executor = Executors.newSingleThreadExecutor(); executor.execute(() -> { From 032cfecbd0dcabfa565d4b5da31182663faba08b Mon Sep 17 00:00:00 2001 From: Emile de Weerd Date: Wed, 2 Aug 2023 17:37:42 +0200 Subject: [PATCH 05/22] kafka-streams: restore the feature name at Quarkus startup Fixes #34171 The FeatureBuildItem was returned in a BuildStep now annotated to only run when native compilation is used. The consequence is that in JVM mode, the FeatureBuildItem is not created, and the feature name not appearing in the start logs of Quarkus. Solution: extract the FeatureBuildItem creation in a dedicated BuildStep. (cherry picked from commit b884a10acdd51740ab2e1aed015e3b9e3d3d9ef5) --- .../streams/deployment/KafkaStreamsProcessor.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java b/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java index 4cc5b52d0b422..7c6ac6e49978e 100644 --- a/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java +++ b/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java @@ -43,17 +43,18 @@ class KafkaStreamsProcessor { public static final String DEFAULT_PARTITION_GROUPER = "org.apache.kafka.streams.processor.DefaultPartitionGrouper"; + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(Feature.KAFKA_STREAMS); + } + @BuildStep(onlyIf = NativeOrNativeSourcesBuild.class) - void build(BuildProducer feature, - BuildProducer reflectiveClasses, + void build(BuildProducer reflectiveClasses, BuildProducer jniRuntimeAccessibleClasses, BuildProducer reinitialized, BuildProducer nativeLibs, LaunchModeBuildItem launchMode, NativeImageRunnerBuildItem nativeImageRunner) throws IOException { - - feature.produce(new FeatureBuildItem(Feature.KAFKA_STREAMS)); - registerClassesThatAreLoadedThroughReflection(reflectiveClasses, launchMode); registerClassesThatAreAccessedViaJni(jniRuntimeAccessibleClasses); addSupportForRocksDbLib(nativeLibs, nativeImageRunner); From bbae178e1e3e2ed78e6f81bc8660f529d35efb9b Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 2 Aug 2023 15:04:58 +0200 Subject: [PATCH 06/22] Propagate all user methods in REST Data with Panache Relates to https://github.com/quarkusio/quarkus/pull/35129#discussion_r1280359385 and https://github.com/quarkusio/quarkus/pull/35129 (cherry picked from commit 42a9853165866e8fcfcde741ec19292c444c8cc6) --- .../deployment/JaxRsResourceImplementor.java | 4 ++-- ...serMethodsWithAnnotationsImplementor.java} | 23 +++++-------------- 2 files changed, 8 insertions(+), 19 deletions(-) rename extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/{UserMethodsWithTransactionalImplementor.java => UserMethodsWithAnnotationsImplementor.java} (70%) diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/JaxRsResourceImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/JaxRsResourceImplementor.java index ecbce04af98a6..462c609f7c29f 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/JaxRsResourceImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/JaxRsResourceImplementor.java @@ -26,7 +26,7 @@ import io.quarkus.rest.data.panache.deployment.methods.ListMethodImplementor; import io.quarkus.rest.data.panache.deployment.methods.MethodImplementor; import io.quarkus.rest.data.panache.deployment.methods.UpdateMethodImplementor; -import io.quarkus.rest.data.panache.deployment.methods.UserMethodsWithTransactionalImplementor; +import io.quarkus.rest.data.panache.deployment.methods.UserMethodsWithAnnotationsImplementor; import io.quarkus.rest.data.panache.deployment.methods.hal.ListHalMethodImplementor; import io.quarkus.rest.data.panache.deployment.properties.ResourceProperties; import io.quarkus.runtime.util.HashUtil; @@ -49,7 +49,7 @@ class JaxRsResourceImplementor { new AddMethodImplementor(capabilities), new UpdateMethodImplementor(capabilities), new DeleteMethodImplementor(capabilities), - new UserMethodsWithTransactionalImplementor(capabilities), + new UserMethodsWithAnnotationsImplementor(), // The list hal endpoint needs to be added for both resteasy classic and resteasy reactive // because the pagination links are programmatically added. new ListHalMethodImplementor(capabilities)); diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UserMethodsWithTransactionalImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UserMethodsWithAnnotationsImplementor.java similarity index 70% rename from extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UserMethodsWithTransactionalImplementor.java rename to extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UserMethodsWithAnnotationsImplementor.java index e1992090435e7..6c27d84c6011c 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UserMethodsWithTransactionalImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UserMethodsWithAnnotationsImplementor.java @@ -1,10 +1,7 @@ package io.quarkus.rest.data.panache.deployment.methods; import org.jboss.jandex.AnnotationTarget; -import org.jboss.jandex.DotName; -import io.quarkus.deployment.Capabilities; -import io.quarkus.deployment.Capability; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.FieldDescriptor; import io.quarkus.gizmo.MethodCreator; @@ -14,26 +11,18 @@ import io.quarkus.rest.data.panache.deployment.properties.ResourceProperties; /** - * Propagate the user methods annotated with `@Transactional`. - * This implementor is only used if Hibernate ORM is present. + * Propagate all the user methods that have annotations. + * + * This is necessary when users use annotations with an interceptor binding like `@Transactional`. */ -public final class UserMethodsWithTransactionalImplementor implements MethodImplementor { - - public static final DotName TRANSACTIONAL = DotName.createSimple("jakarta.transaction.Transactional"); - - private final Capabilities capabilities; - - public UserMethodsWithTransactionalImplementor(Capabilities capabilities) { - this.capabilities = capabilities; - } +public final class UserMethodsWithAnnotationsImplementor implements MethodImplementor { @Override public void implement(ClassCreator classCreator, ResourceMetadata resourceMetadata, ResourceProperties resourceProperties, FieldDescriptor resourceField) { - if (capabilities.isPresent(Capability.HIBERNATE_ORM) && resourceMetadata.getResourceInterface() != null) { + if (resourceMetadata.getResourceInterface() != null) { for (var methodInfo : resourceMetadata.getResourceInterface().methods()) { - // we only need to propagate the user methods annotated with `@Transactional` - if (methodInfo.hasAnnotation(TRANSACTIONAL)) { + if (methodInfo.isDefault() && !methodInfo.annotations().isEmpty()) { MethodCreator methodCreator = classCreator.getMethodCreator(MethodDescriptor.of(methodInfo)); methodCreator.setSignature(methodInfo.genericSignatureIfRequired()); for (var annotation : methodInfo.annotations()) { From d8408bc536f46eaac5ef13657c06b5853137965f Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Thu, 15 Jun 2023 17:14:51 +0200 Subject: [PATCH 07/22] Extract update info as data structures (cherry picked from commit e9006c1f767abcb760c90574254c6175b0f1b070) --- .../handlers/ProjectInfoCommandHandler.java | 159 +------ .../handlers/UpdateProjectCommandHandler.java | 418 +++--------------- .../devtools/project/state/ProjectStates.java | 116 +++++ .../project/update/ExtensionMapBuilder.java | 115 +++++ .../project/update/ExtensionUpdateInfo.java | 33 ++ .../devtools/project/update/PlatformInfo.java | 49 ++ .../update/ProjectExtensionsUpdateInfo.java | 45 ++ .../update/ProjectPlatformUpdateInfo.java | 43 ++ .../project/update/ProjectUpdateInfos.java | 269 +++++++++++ .../{ => rewrite}/QuarkusUpdateCommand.java | 4 +- .../{ => rewrite}/QuarkusUpdateException.java | 2 +- .../{ => rewrite}/QuarkusUpdateRecipe.java | 2 +- .../{ => rewrite}/QuarkusUpdateRecipeIO.java | 2 +- .../update/{ => rewrite}/QuarkusUpdates.java | 8 +- .../QuarkusUpdatesRepository.java | 2 +- .../{ => rewrite}/RewriteOperation.java | 2 +- .../UpdateDependencyVersionOperation.java | 4 +- ...dateManagedDependencyVersionOperation.java | 4 +- .../operations/UpdatePropertyOperation.java | 4 +- .../UpgradeGradlePluginOperation.java | 4 +- .../QuarkusUpdateRecipeIOTest.java | 6 +- .../QuarkusUpdatesRepositoryTest.java | 4 +- 22 files changed, 770 insertions(+), 525 deletions(-) create mode 100644 independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ProjectStates.java create mode 100644 independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ExtensionMapBuilder.java create mode 100644 independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ExtensionUpdateInfo.java create mode 100644 independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/PlatformInfo.java create mode 100644 independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectExtensionsUpdateInfo.java create mode 100644 independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectPlatformUpdateInfo.java create mode 100644 independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectUpdateInfos.java rename independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/{ => rewrite}/QuarkusUpdateCommand.java (98%) rename independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/{ => rewrite}/QuarkusUpdateException.java (85%) rename independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/{ => rewrite}/QuarkusUpdateRecipe.java (97%) rename independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/{ => rewrite}/QuarkusUpdateRecipeIO.java (97%) rename independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/{ => rewrite}/QuarkusUpdates.java (89%) rename independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/{ => rewrite}/QuarkusUpdatesRepository.java (99%) rename independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/{ => rewrite}/RewriteOperation.java (88%) rename independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/{ => rewrite}/operations/UpdateDependencyVersionOperation.java (87%) rename independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/{ => rewrite}/operations/UpdateManagedDependencyVersionOperation.java (89%) rename independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/{ => rewrite}/operations/UpdatePropertyOperation.java (86%) rename independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/{ => rewrite}/operations/UpgradeGradlePluginOperation.java (86%) rename independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/{ => rewrite}/QuarkusUpdateRecipeIOTest.java (86%) rename independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/{ => rewrite}/QuarkusUpdatesRepositoryTest.java (79%) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ProjectInfoCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ProjectInfoCommandHandler.java index 786b9de83d105..e05ade2a0acc4 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ProjectInfoCommandHandler.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ProjectInfoCommandHandler.java @@ -2,20 +2,17 @@ import static io.quarkus.devtools.messagewriter.MessageIcons.OUT_OF_DATE_ICON; import static io.quarkus.devtools.messagewriter.MessageIcons.UP_TO_DATE_ICON; +import static io.quarkus.devtools.project.state.ProjectStates.resolveProjectState; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.workspace.WorkspaceModule; -import io.quarkus.bootstrap.workspace.WorkspaceModuleId; import io.quarkus.devtools.commands.ProjectInfo; import io.quarkus.devtools.commands.data.QuarkusCommandException; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; @@ -25,12 +22,9 @@ import io.quarkus.devtools.project.state.ModuleState; import io.quarkus.devtools.project.state.ProjectState; import io.quarkus.devtools.project.state.TopExtensionDependency; +import io.quarkus.devtools.project.update.PlatformInfo; import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactKey; -import io.quarkus.maven.dependency.Dependency; -import io.quarkus.maven.dependency.DependencyFlags; -import io.quarkus.maven.dependency.ResolvedDependency; -import io.quarkus.registry.catalog.ExtensionCatalog; import io.quarkus.registry.catalog.ExtensionOrigin; public class ProjectInfoCommandHandler implements QuarkusCommandHandler { @@ -58,13 +52,17 @@ protected static boolean logState(ProjectState projectState, boolean perModule, final Map providerInfo = new LinkedHashMap<>(); for (ArtifactCoords bom : projectState.getPlatformBoms()) { - providerInfo.computeIfAbsent(bom.getKey(), k -> new PlatformInfo()).imported = bom; + providerInfo.computeIfAbsent(bom.getKey(), k -> new PlatformInfo(bom, null)); } for (TopExtensionDependency dep : projectState.getExtensions()) { final ExtensionOrigin origin = dep.getOrigin(); if (origin != null && origin.isPlatform()) { - providerInfo.computeIfAbsent(origin.getBom().getKey(), k -> new PlatformInfo()).recommended = origin - .getBom(); + providerInfo.compute(origin.getBom().getKey(), (k, v) -> { + if (v == null) { + return new PlatformInfo(null, origin.getBom()); + } + return new PlatformInfo(v.getImported(), origin.getBom()); + }); } } @@ -74,19 +72,19 @@ protected static boolean logState(ProjectState projectState, boolean perModule, log.info("Quarkus platform BOMs:"); boolean recommendExtraImports = false; for (PlatformInfo platform : providerInfo.values()) { - if (platform.imported == null) { + if (!platform.isImported()) { recommendExtraImports = true; continue; } final StringBuilder sb = new StringBuilder(); - if (platform.recommended == null) { + if (platform.getRecommended() == null) { if (rectify) { sb.append(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, - UpdateProjectCommandHandler.REMOVE, platform.imported.toCompactCoords())); + UpdateProjectCommandHandler.REMOVE, platform.getImported().toCompactCoords())); recommendationsAvailable = true; } else { sb.append(" "); - sb.append(platform.imported.toCompactCoords()); + sb.append(platform.getImported().toCompactCoords()); if (!projectState.getExtensions().isEmpty()) { // The extension check is for modules that are aggregating modules (e.g. parent POMs) // that import common BOMs. It's however not how it should be done. @@ -96,9 +94,9 @@ protected static boolean logState(ProjectState projectState, boolean perModule, } } else if (platform.isVersionUpdateRecommended()) { sb.append(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, - rectify ? UpdateProjectCommandHandler.UPDATE : "", platform.imported.toCompactCoords())); + rectify ? UpdateProjectCommandHandler.UPDATE : "", platform.getImported().toCompactCoords())); if (rectify) { - sb.append(platform.imported.toCompactCoords()).append(" -> ") + sb.append(platform.getImported().toCompactCoords()).append(" -> ") .append(platform.getRecommendedVersion()); } else { sb.append(" ").append(OUT_OF_DATE_ICON.iconOrMessage()); @@ -106,17 +104,17 @@ protected static boolean logState(ProjectState projectState, boolean perModule, recommendationsAvailable = true; } else { sb.append(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, "", - platform.imported.toCompactCoords())) + platform.getImported().toCompactCoords())) .append(" ").append(UP_TO_DATE_ICON.iconOrMessage()); } log.info(sb.toString()); } if (rectify && recommendExtraImports) { for (PlatformInfo platform : providerInfo.values()) { - if (platform.imported == null) { + if (platform.getImported() == null) { log.info(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, UpdateProjectCommandHandler.ADD, - platform.recommended.toCompactCoords())); + platform.getRecommended().toCompactCoords())); } } recommendationsAvailable = true; @@ -299,125 +297,4 @@ private static boolean logModuleInfo(ProjectState project, ModuleState module, P return recommendationsAvailable; } - protected static ProjectState resolveProjectState(ApplicationModel appModel, ExtensionCatalog currentCatalog) { - final ProjectState.Builder projectBuilder = ProjectState.builder(); - - final Collection importedPlatformBoms = appModel.getPlatforms().getImportedPlatformBoms(); - if (importedPlatformBoms.isEmpty()) { - return projectBuilder.build(); - } - - final Map extProviderBuilders = new LinkedHashMap<>(importedPlatformBoms.size()); - importedPlatformBoms.forEach(bom -> { - projectBuilder.addPlatformBom(bom); - extProviderBuilders.put(ExtensionProvider.key(bom, true), - ExtensionProvider.builder().setArtifact(bom).setPlatform(true)); - }); - - final Map projectModuleBuilders = new HashMap<>(); - final Map> directModuleDeps = new HashMap<>(); - - final WorkspaceModule appModule = appModel.getAppArtifact().getWorkspaceModule(); - if (appModule != null) { - final ModuleState.Builder module = ModuleState.builder().setWorkspaceModule(appModule).setMainModule(true); - projectModuleBuilders.put(appModule.getId(), module); - appModule.getDirectDependencies() - .forEach(d -> directModuleDeps.computeIfAbsent(d.getKey(), dk -> new ArrayList<>()).add(module)); - - for (Dependency constraint : appModule.getDirectDependencyConstraints()) { - if (extProviderBuilders.containsKey(constraint.toCompactCoords())) { - module.addPlatformBom(constraint); - } - } - - } - for (ResolvedDependency dep : appModel.getDependencies()) { - if (dep.getWorkspaceModule() != null) { - projectModuleBuilders.computeIfAbsent(dep.getWorkspaceModule().getId(), k -> { - final ModuleState.Builder module = ModuleState.builder() - .setWorkspaceModule(dep.getWorkspaceModule()); - dep.getWorkspaceModule().getDirectDependencies().forEach( - d -> directModuleDeps.computeIfAbsent(d.getKey(), dk -> new ArrayList<>()).add(module)); - return module; - }); - } - } - - final Map directExtDeps = new HashMap<>(); - for (ResolvedDependency dep : appModel.getDependencies()) { - if (dep.isFlagSet(DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT)) { - directExtDeps.put(dep.getKey(), TopExtensionDependency.builder().setResolvedDependency(dep) - .setTransitive(!directModuleDeps.containsKey(dep.getKey()))); - } else if (dep.isRuntimeExtensionArtifact() && directModuleDeps.containsKey(dep.getKey())) { - directExtDeps.put(dep.getKey(), TopExtensionDependency.builder().setResolvedDependency(dep)); - } - } - - if (directExtDeps.isEmpty()) { - return projectBuilder.build(); - } - - currentCatalog.getExtensions().forEach(e -> { - final ArtifactKey key = e.getArtifact().getKey(); - final TopExtensionDependency.Builder dep = directExtDeps.get(key); - if (dep != null) { - dep.setCatalogMetadata(e); - } - }); - - for (TopExtensionDependency.Builder extBuilder : directExtDeps.values()) { - final List modules = directModuleDeps.getOrDefault(extBuilder.getKey(), - Collections.emptyList()); - final TopExtensionDependency dep = extBuilder.setTransitive(modules.isEmpty()).build(); - projectBuilder.addExtensionDependency(dep); - for (ModuleState.Builder module : modules) { - module.addExtensionDependency(dep); - } - final ExtensionProvider.Builder provider = extProviderBuilders.computeIfAbsent(dep.getProviderKey(), - k -> ExtensionProvider.builder().setOrigin(dep.getOrigin())); - provider.addExtension(dep); - } - - for (ExtensionProvider.Builder builder : extProviderBuilders.values()) { - projectBuilder.addExtensionProvider(builder.build()); - } - - for (ModuleState.Builder builder : projectModuleBuilders.values()) { - projectBuilder.addModule(builder.build()); - } - - return projectBuilder.build(); - } - - static class PlatformInfo { - ArtifactCoords imported; - ArtifactCoords recommended; - - boolean isVersionUpdateRecommended() { - return imported != null && recommended != null && !imported.getVersion().equals(recommended.getVersion()); - } - - String getRecommendedVersion() { - return recommended == null ? null : recommended.getVersion(); - } - - boolean isImported() { - return imported != null; - } - - boolean isToBeImported() { - return imported == null && recommended != null; - } - - ArtifactCoords getRecommendedCoords() { - return recommended == null ? imported : recommended; - } - - String getRecommendedProviderKey() { - if (recommended != null) { - return ExtensionProvider.key(recommended, true); - } - return ExtensionProvider.key(imported, true); - } - } } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java index 2ad9299d5105d..187733c7e2abd 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java @@ -1,46 +1,39 @@ package io.quarkus.devtools.commands.handlers; +import static io.quarkus.devtools.project.state.ProjectStates.resolveProjectState; +import static io.quarkus.devtools.project.update.ProjectUpdateInfos.resolvePlatformUpdateInfo; +import static io.quarkus.devtools.project.update.ProjectUpdateInfos.resolveRecommendedState; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.stream.Collectors; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.devtools.commands.UpdateProject; import io.quarkus.devtools.commands.data.QuarkusCommandException; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; -import io.quarkus.devtools.commands.handlers.ProjectInfoCommandHandler.PlatformInfo; import io.quarkus.devtools.messagewriter.MessageWriter; import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.QuarkusProject; import io.quarkus.devtools.project.QuarkusProjectHelper; -import io.quarkus.devtools.project.state.ExtensionProvider; -import io.quarkus.devtools.project.state.ModuleState; import io.quarkus.devtools.project.state.ProjectState; -import io.quarkus.devtools.project.state.TopExtensionDependency; -import io.quarkus.devtools.project.update.QuarkusUpdateCommand; -import io.quarkus.devtools.project.update.QuarkusUpdateException; -import io.quarkus.devtools.project.update.QuarkusUpdates; -import io.quarkus.devtools.project.update.QuarkusUpdatesRepository; +import io.quarkus.devtools.project.update.ExtensionUpdateInfo; +import io.quarkus.devtools.project.update.PlatformInfo; +import io.quarkus.devtools.project.update.ProjectExtensionsUpdateInfo; +import io.quarkus.devtools.project.update.ProjectPlatformUpdateInfo; +import io.quarkus.devtools.project.update.ProjectUpdateInfos; +import io.quarkus.devtools.project.update.rewrite.QuarkusUpdateCommand; +import io.quarkus.devtools.project.update.rewrite.QuarkusUpdateException; +import io.quarkus.devtools.project.update.rewrite.QuarkusUpdates; +import io.quarkus.devtools.project.update.rewrite.QuarkusUpdatesRepository; import io.quarkus.maven.dependency.ArtifactCoords; -import io.quarkus.maven.dependency.ArtifactKey; import io.quarkus.platform.tools.ToolsConstants; -import io.quarkus.registry.catalog.Extension; import io.quarkus.registry.catalog.ExtensionCatalog; -import io.quarkus.registry.catalog.ExtensionOrigin; -import io.quarkus.registry.catalog.selection.ExtensionOrigins; -import io.quarkus.registry.catalog.selection.OriginCombination; -import io.quarkus.registry.catalog.selection.OriginPreference; -import io.quarkus.registry.catalog.selection.OriginSelector; public class UpdateProjectCommandHandler implements QuarkusCommandHandler { public static final String ADD = "Add:"; @@ -55,7 +48,7 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws final String targetPlatformVersion = invocation.getValue(UpdateProject.TARGET_PLATFORM_VERSION); final boolean perModule = invocation.getValue(UpdateProject.PER_MODULE, false); - final ProjectState currentState = ProjectInfoCommandHandler.resolveProjectState(appModel, + final ProjectState currentState = resolveProjectState(appModel, invocation.getQuarkusProject().getExtensionsCatalog()); final ArtifactCoords projectQuarkusPlatformBom = getProjectQuarkusPlatformBOM(currentState); if (projectQuarkusPlatformBom == null) { @@ -68,7 +61,15 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws invocation.log().info("Instructions to update this project from '%s' to '%s':", projectQuarkusPlatformBom.getVersion(), targetPlatformVersion); final QuarkusProject quarkusProject = invocation.getQuarkusProject(); - logUpdates(currentState, targetCatalog, false, perModule, quarkusProject.log()); + final ProjectState recommendedState = resolveRecommendedState(currentState, targetCatalog, invocation.log()); + final ProjectPlatformUpdateInfo platformUpdateInfo = resolvePlatformUpdateInfo(currentState, + recommendedState); + final ProjectExtensionsUpdateInfo extensionsUpdateInfo = ProjectUpdateInfos.resolveExtensionsUpdateInfo( + currentState, + recommendedState); + + logUpdates(currentState, recommendedState, platformUpdateInfo, extensionsUpdateInfo, false, perModule, + quarkusProject.log()); final boolean noRewrite = invocation.getValue(UpdateProject.NO_REWRITE, false); if (!noRewrite) { @@ -130,7 +131,9 @@ private static ArtifactCoords getProjectQuarkusPlatformBOM(ProjectState currentS return null; } - private static void logUpdates(ProjectState currentState, ExtensionCatalog recommendedCatalog, boolean recommendState, + private static void logUpdates(ProjectState currentState, ProjectState recommendedState, + ProjectPlatformUpdateInfo platformUpdateInfo, + ProjectExtensionsUpdateInfo extensionsUpdateInfo, boolean recommendState, boolean perModule, MessageWriter log) { if (currentState.getPlatformBoms().isEmpty()) { log.info("The project does not import any Quarkus platform BOM"); @@ -140,7 +143,6 @@ private static void logUpdates(ProjectState currentState, ExtensionCatalog recom log.info("Quarkus extension were not found among the project dependencies"); return; } - final ProjectState recommendedState = resolveRecommendedState(currentState, recommendedCatalog, log); if (currentState == recommendedState) { log.info("The project is up-to-date"); return; @@ -151,151 +153,83 @@ private static void logUpdates(ProjectState currentState, ExtensionCatalog recom return; } - // log instructions - final Map platformImports = new LinkedHashMap<>(); - for (ArtifactCoords c : currentState.getPlatformBoms()) { - final PlatformInfo info = new PlatformInfo(); - info.imported = c; - platformImports.put(c.getKey(), info); - } - List importVersionUpdates = new ArrayList<>(); - List newImports = new ArrayList<>(0); - for (ArtifactCoords c : recommendedState.getPlatformBoms()) { - final PlatformInfo importInfo = platformImports.computeIfAbsent(c.getKey(), k -> new PlatformInfo()); - importInfo.recommended = c; - if (importInfo.isToBeImported()) { - newImports.add(importInfo); - } else if (importInfo.isVersionUpdateRecommended()) { - importVersionUpdates.add(importInfo); - } - } - - log.info(""); - final boolean importsToBeRemoved = platformImports.values().stream().filter(p -> p.recommended == null).findFirst() - .isPresent(); - final boolean platformUpdatesAvailable = !importVersionUpdates.isEmpty() || !newImports.isEmpty() || importsToBeRemoved; - if (platformUpdatesAvailable) { + if (platformUpdateInfo.isPlatformUpdatesAvailable()) { log.info("Recommended Quarkus platform BOM updates:"); - if (!importVersionUpdates.isEmpty()) { - for (PlatformInfo importInfo : importVersionUpdates) { + if (!platformUpdateInfo.getImportVersionUpdates().isEmpty()) { + for (PlatformInfo importInfo : platformUpdateInfo.getImportVersionUpdates()) { log.info(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, - UpdateProjectCommandHandler.UPDATE, importInfo.imported.toCompactCoords()) + " -> " + UpdateProjectCommandHandler.UPDATE, importInfo.getImported().toCompactCoords()) + " -> " + importInfo.getRecommendedVersion()); } } - if (!newImports.isEmpty()) { - for (PlatformInfo importInfo : newImports) { + if (!platformUpdateInfo.getNewImports().isEmpty()) { + for (PlatformInfo importInfo : platformUpdateInfo.getNewImports()) { log.info(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, - UpdateProjectCommandHandler.ADD, importInfo.recommended.toCompactCoords())); + UpdateProjectCommandHandler.ADD, importInfo.getRecommended().toCompactCoords())); } } - if (importsToBeRemoved) { - for (PlatformInfo importInfo : platformImports.values()) { - if (importInfo.recommended == null) { + if (platformUpdateInfo.isImportsToBeRemoved()) { + for (PlatformInfo importInfo : platformUpdateInfo.getPlatformImports().values()) { + if (importInfo.getRecommended() == null) { log.info(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, - UpdateProjectCommandHandler.REMOVE, importInfo.imported.toCompactCoords())); + UpdateProjectCommandHandler.REMOVE, importInfo.getImported().toCompactCoords())); } } } log.info(""); } - final ExtensionMap extensionInfo = new ExtensionMap(currentState.getExtensions().size()); - for (TopExtensionDependency dep : currentState.getExtensions()) { - extensionInfo.add(new ExtensionInfo(dep)); - } - for (TopExtensionDependency dep : recommendedState.getExtensions()) { - final ExtensionInfo info = extensionInfo.get(dep.getKey()); - if (info != null) { - info.recommendedDep = dep; - } - } - final Map> versionedManagedExtensions = new LinkedHashMap<>(0); - final Map> removedExtensions = new LinkedHashMap<>(0); - final Map> addedExtensions = new LinkedHashMap<>(0); - final Map> nonPlatformExtensionUpdates = new LinkedHashMap<>(); - for (ExtensionInfo info : extensionInfo.values()) { - if (!info.isUpdateRecommended()) { - continue; - } - if (!info.currentDep.getKey().equals(info.getRecommendedDependency().getKey())) { - if (info.currentDep.isPlatformExtension()) { - removedExtensions.computeIfAbsent(info.currentDep.getProviderKey(), k -> new ArrayList<>()) - .add(info.currentDep.getArtifact()); - } else { - nonPlatformExtensionUpdates.computeIfAbsent(info.currentDep.getProviderKey(), k -> new ArrayList<>()) - .add(info); - } - if (info.getRecommendedDependency().isPlatformExtension()) { - addedExtensions.computeIfAbsent(info.getRecommendedDependency().getProviderKey(), k -> new ArrayList<>()) - .add(info.getRecommendedDependency().getArtifact()); - } else { - nonPlatformExtensionUpdates - .computeIfAbsent(info.getRecommendedDependency().getProviderKey(), k -> new ArrayList<>()) - .add(info); - } - } else if (info.getRecommendedDependency().isPlatformExtension()) { - if (info.currentDep.isNonRecommendedVersion()) { - versionedManagedExtensions - .computeIfAbsent(info.getRecommendedDependency().getProviderKey(), k -> new ArrayList<>()) - .add(info); - } - } else if (!info.currentDep.getVersion().equals(info.getRecommendedDependency().getVersion())) { - nonPlatformExtensionUpdates - .computeIfAbsent(info.getRecommendedDependency().getProviderKey(), k -> new ArrayList<>()).add(info); - } - } - - if (versionedManagedExtensions.isEmpty() - && removedExtensions.isEmpty() - && addedExtensions.isEmpty() - && nonPlatformExtensionUpdates.isEmpty()) { - if (!platformUpdatesAvailable) { + if (extensionsUpdateInfo.isEmpty()) { + if (!platformUpdateInfo.isPlatformUpdatesAvailable()) { log.info("The project is up-to-date"); } return; } - for (PlatformInfo platform : platformImports.values()) { + for (PlatformInfo platform : platformUpdateInfo.getPlatformImports().values()) { final String provider = platform.getRecommendedProviderKey(); - if (!versionedManagedExtensions.containsKey(provider) - && !removedExtensions.containsKey(provider) - && !addedExtensions.containsKey(provider)) { + if (!extensionsUpdateInfo.getVersionedManagedExtensions().containsKey(provider) + && !extensionsUpdateInfo.getRemovedExtensions().containsKey(provider) + && !extensionsUpdateInfo.getAddedExtensions().containsKey(provider)) { continue; } log.info("Extensions from " + platform.getRecommendedProviderKey() + ":"); - for (ExtensionInfo e : versionedManagedExtensions.getOrDefault(provider, Collections.emptyList())) { + for (ExtensionUpdateInfo e : extensionsUpdateInfo.getVersionedManagedExtensions().getOrDefault(provider, + Collections.emptyList())) { final StringBuilder sb = new StringBuilder(); sb.append(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, - UpdateProjectCommandHandler.UPDATE, e.currentDep.getArtifact().toCompactCoords())); + UpdateProjectCommandHandler.UPDATE, e.getCurrentDep().getArtifact().toCompactCoords())); sb.append(" -> remove version (managed)"); log.info(sb.toString()); } - for (ArtifactCoords e : addedExtensions.getOrDefault(provider, Collections.emptyList())) { + for (ArtifactCoords e : extensionsUpdateInfo.getAddedExtensions().getOrDefault(provider, Collections.emptyList())) { log.info(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, UpdateProjectCommandHandler.ADD, e.getKey().toGacString())); } - for (ArtifactCoords e : removedExtensions.getOrDefault(provider, Collections.emptyList())) { + for (ArtifactCoords e : extensionsUpdateInfo.getRemovedExtensions().getOrDefault(provider, + Collections.emptyList())) { log.info(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, UpdateProjectCommandHandler.REMOVE, e.getKey().toGacString())); } log.info(""); } - if (!nonPlatformExtensionUpdates.isEmpty()) { - for (Map.Entry> provider : nonPlatformExtensionUpdates.entrySet()) { + if (!extensionsUpdateInfo.getNonPlatformExtensions().isEmpty()) { + for (Map.Entry> provider : extensionsUpdateInfo.getNonPlatformExtensions() + .entrySet()) { log.info("Extensions from " + provider.getKey() + ":"); - for (ExtensionInfo info : provider.getValue()) { - if (info.currentDep.isPlatformExtension()) { + for (ExtensionUpdateInfo info : provider.getValue()) { + if (info.getCurrentDep().isPlatformExtension()) { log.info(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, UpdateProjectCommandHandler.ADD, info.getRecommendedDependency().getArtifact().toCompactCoords())); } else if (info.getRecommendedDependency().isPlatformExtension()) { log.info(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, - UpdateProjectCommandHandler.REMOVE, info.currentDep.getArtifact().toCompactCoords())); + UpdateProjectCommandHandler.REMOVE, info.getCurrentDep().getArtifact().toCompactCoords())); } else { log.info(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, - UpdateProjectCommandHandler.UPDATE, info.currentDep.getArtifact().toCompactCoords() + " -> " + UpdateProjectCommandHandler.UPDATE, + info.getCurrentDep().getArtifact().toCompactCoords() + " -> " + info.getRecommendedDependency().getVersion())); } } @@ -304,243 +238,6 @@ private static void logUpdates(ProjectState currentState, ExtensionCatalog recom } } - private static ProjectState resolveRecommendedState(ProjectState currentState, ExtensionCatalog latestCatalog, - MessageWriter log) { - if (currentState.getPlatformBoms().isEmpty()) { - return currentState; - } - if (currentState.getExtensions().isEmpty()) { - return currentState; - } - - final ExtensionMap extensionInfo = new ExtensionMap(); - for (TopExtensionDependency dep : currentState.getExtensions()) { - extensionInfo.add(new ExtensionInfo(dep)); - } - - for (Extension e : latestCatalog.getExtensions()) { - final ExtensionInfo candidate = extensionInfo.get(e.getArtifact().getKey()); - if (candidate != null && candidate.latestMetadata == null) { - // if the latestMetadata has already been initialized, it's already the preferred one - // that could happen if an artifact has relocated - candidate.latestMetadata = e; - } - } - - final List unknownExtensions = new ArrayList<>(0); - final List updateCandidates = new ArrayList<>(extensionInfo.size()); - final Map updateCandidatesByOrigin = new HashMap<>(); - for (ExtensionInfo i : extensionInfo.values()) { - if (i.latestMetadata == null) { - unknownExtensions.add(i); - } else { - updateCandidates.add(i.latestMetadata); - for (ExtensionOrigin o : i.latestMetadata.getOrigins()) { - updateCandidatesByOrigin.computeIfAbsent(o.getId(), k -> new ExtensionMap()).add(i); - } - } - } - - if (extensionInfo.isEmpty()) { - return currentState; - } - - if (!unknownExtensions.isEmpty()) { - log.warn( - "The configured Quarkus registries did not provide any compatibility information for the following extensions in the context of the currently recommended Quarkus platforms:"); - unknownExtensions.forEach(e -> log.warn(" " + e.currentDep.getArtifact().toCompactCoords())); - } - - final List recommendedOrigins; - try { - recommendedOrigins = getRecommendedOrigins(latestCatalog, updateCandidates); - } catch (QuarkusCommandException e) { - log.info("Failed to find a compatible configuration update for the project"); - return currentState; - } - - int collectedUpdates = 0; - for (ExtensionCatalog recommendedOrigin : recommendedOrigins) { - final ExtensionMap candidates = updateCandidatesByOrigin.get(recommendedOrigin.getId()); - for (Extension e : recommendedOrigin.getExtensions()) { - final ExtensionInfo info = candidates.get(e.getArtifact().getKey()); - if (info != null && info.recommendedMetadata == null) { - info.setRecommendedMetadata(e); - if (++collectedUpdates == updateCandidates.size()) { - break; - } - } - } - } - - final ProjectState.Builder stateBuilder = ProjectState.builder(); - for (ExtensionCatalog c : recommendedOrigins) { - if (c.isPlatform()) { - stateBuilder.addPlatformBom(c.getBom()); - } - } - - final Map extProviders = new LinkedHashMap<>(recommendedOrigins.size()); - for (ExtensionInfo info : extensionInfo.values()) { - final TopExtensionDependency ext = info.getRecommendedDependency(); - stateBuilder.addExtensionDependency(ext); - extProviders.computeIfAbsent(ext.getProviderKey(), k -> ExtensionProvider.builder().setOrigin(ext.getOrigin())) - .addExtension(ext); - } - - extProviders.values().forEach(b -> stateBuilder.addExtensionProvider(b.build())); - - for (ModuleState module : currentState.getModules()) { - final ModuleState.Builder moduleBuilder = ModuleState.builder() - .setMainModule(module.isMain()) - .setWorkspaceModule(module.getWorkspaceModule()); - for (TopExtensionDependency dep : module.getExtensions()) { - final TopExtensionDependency recommendedDep = extensionInfo.get(dep.getKey()).getRecommendedDependency(); - moduleBuilder.addExtensionDependency(recommendedDep); - final ExtensionOrigin origin = recommendedDep.getOrigin(); - if (origin != null && origin.isPlatform()) { - moduleBuilder.addPlatformBom(origin.getBom()); - } - } - stateBuilder.addModule(moduleBuilder.build()); - } - - return stateBuilder.build(); - } - - private static class ExtensionInfo { - final TopExtensionDependency currentDep; - Extension latestMetadata; - Extension recommendedMetadata; - TopExtensionDependency recommendedDep; - - ExtensionInfo(TopExtensionDependency currentDep) { - this.currentDep = currentDep; - } - - void setRecommendedMetadata(Extension e) { - this.recommendedMetadata = e; - if (!currentDep.getArtifact().getKey().equals(e.getArtifact().getKey())) { - } - } - - Extension getRecommendedMetadata() { - if (recommendedMetadata != null) { - return recommendedMetadata; - } - if (recommendedDep == null) { - return currentDep.getCatalogMetadata(); - } - return recommendedMetadata = currentDep.getCatalogMetadata(); - } - - TopExtensionDependency getRecommendedDependency() { - if (recommendedDep != null) { - return recommendedDep; - } - if (recommendedMetadata == null) { - return currentDep; - } - return recommendedDep = TopExtensionDependency.builder() - .setArtifact(recommendedMetadata.getArtifact()) - .setCatalogMetadata(recommendedMetadata) - .setTransitive(currentDep.isTransitive()) - .build(); - } - - boolean isUpdateRecommended() { - return getRecommendedDependency() != currentDep; - } - } - - private static List getRecommendedOrigins(ExtensionCatalog extensionCatalog, List extensions) - throws QuarkusCommandException { - final List extOrigins = new ArrayList<>(extensions.size()); - for (Extension e : extensions) { - addOrigins(extOrigins, e); - } - - final OriginCombination recommendedCombination = OriginSelector.of(extOrigins).calculateRecommendedCombination(); - if (recommendedCombination == null) { - final StringBuilder buf = new StringBuilder(); - buf.append("Failed to determine a compatible Quarkus version for the requested extensions: "); - buf.append(extensions.get(0).getArtifact().getKey().toGacString()); - for (int i = 1; i < extensions.size(); ++i) { - buf.append(", ").append(extensions.get(i).getArtifact().getKey().toGacString()); - } - throw new QuarkusCommandException(buf.toString()); - } - return recommendedCombination.getUniqueSortedOrigins().stream().map(o -> o.getCatalog()).collect(Collectors.toList()); - } - - private static void addOrigins(final List extOrigins, Extension e) { - ExtensionOrigins.Builder eoBuilder = null; - for (ExtensionOrigin o : e.getOrigins()) { - if (!(o instanceof ExtensionCatalog)) { - continue; - } - final ExtensionCatalog c = (ExtensionCatalog) o; - final OriginPreference op = (OriginPreference) c.getMetadata().get("origin-preference"); - if (op == null) { - continue; - } - if (eoBuilder == null) { - eoBuilder = ExtensionOrigins.builder(e.getArtifact().getKey()); - } - eoBuilder.addOrigin(c, op); - } - if (eoBuilder != null) { - extOrigins.add(eoBuilder.build()); - } - } - - private static class ExtensionMap { - final Map> extensionInfo; - final List list = new ArrayList<>(); - - ExtensionMap() { - this.extensionInfo = new LinkedHashMap<>(); - } - - ExtensionMap(int size) { - this.extensionInfo = new LinkedHashMap<>(size); - } - - void add(ExtensionInfo e) { - extensionInfo.put(e.currentDep.getArtifact().getArtifactId(), Collections.singletonList(e)); - list.add(e); - } - - ExtensionInfo get(ArtifactKey key) { - final List list = extensionInfo.get(key.getArtifactId()); - if (list == null || list.isEmpty()) { - return null; - } - if (list.size() == 1) { - return list.get(0); - } - for (ExtensionInfo e : list) { - if (e.currentDep.getKey().equals(key) - || e.getRecommendedDependency() != null && e.getRecommendedDependency().getKey().equals(key)) { - return e; - } - } - throw new IllegalArgumentException(key + " isn't found in the extension map"); - } - - Collection values() { - return list; - } - - int size() { - return extensionInfo.size(); - } - - boolean isEmpty() { - return extensionInfo.isEmpty(); - } - } - @SuppressWarnings({ "rawtypes", "unchecked" }) private T getMetadata(ExtensionCatalog catalog, String... path) { Object currentValue = catalog.getMetadata(); @@ -554,4 +251,5 @@ private T getMetadata(ExtensionCatalog catalog, String... path) { return (T) currentValue; } + } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ProjectStates.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ProjectStates.java new file mode 100644 index 0000000000000..1364dbb3eee2e --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ProjectStates.java @@ -0,0 +1,116 @@ +package io.quarkus.devtools.project.state; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import io.quarkus.bootstrap.model.ApplicationModel; +import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.bootstrap.workspace.WorkspaceModuleId; +import io.quarkus.maven.dependency.ArtifactCoords; +import io.quarkus.maven.dependency.ArtifactKey; +import io.quarkus.maven.dependency.Dependency; +import io.quarkus.maven.dependency.DependencyFlags; +import io.quarkus.maven.dependency.ResolvedDependency; +import io.quarkus.registry.catalog.ExtensionCatalog; + +public final class ProjectStates { + + private ProjectStates() { + } + + public static ProjectState resolveProjectState(ApplicationModel appModel, ExtensionCatalog currentCatalog) { + final ProjectState.Builder projectBuilder = ProjectState.builder(); + + final Collection importedPlatformBoms = appModel.getPlatforms().getImportedPlatformBoms(); + if (importedPlatformBoms.isEmpty()) { + return projectBuilder.build(); + } + + final Map extProviderBuilders = new LinkedHashMap<>(importedPlatformBoms.size()); + importedPlatformBoms.forEach(bom -> { + projectBuilder.addPlatformBom(bom); + extProviderBuilders.put(ExtensionProvider.key(bom, true), + ExtensionProvider.builder().setArtifact(bom).setPlatform(true)); + }); + + final Map projectModuleBuilders = new HashMap<>(); + final Map> directModuleDeps = new HashMap<>(); + + final WorkspaceModule appModule = appModel.getAppArtifact().getWorkspaceModule(); + if (appModule != null) { + final ModuleState.Builder module = ModuleState.builder().setWorkspaceModule(appModule).setMainModule(true); + projectModuleBuilders.put(appModule.getId(), module); + appModule.getDirectDependencies() + .forEach(d -> directModuleDeps.computeIfAbsent(d.getKey(), dk -> new ArrayList<>()).add(module)); + + for (Dependency constraint : appModule.getDirectDependencyConstraints()) { + if (extProviderBuilders.containsKey(constraint.toCompactCoords())) { + module.addPlatformBom(constraint); + } + } + + } + for (ResolvedDependency dep : appModel.getDependencies()) { + if (dep.getWorkspaceModule() != null) { + projectModuleBuilders.computeIfAbsent(dep.getWorkspaceModule().getId(), k -> { + final ModuleState.Builder module = ModuleState.builder() + .setWorkspaceModule(dep.getWorkspaceModule()); + dep.getWorkspaceModule().getDirectDependencies().forEach( + d -> directModuleDeps.computeIfAbsent(d.getKey(), dk -> new ArrayList<>()).add(module)); + return module; + }); + } + } + + final Map directExtDeps = new HashMap<>(); + for (ResolvedDependency dep : appModel.getDependencies()) { + if (dep.isFlagSet(DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT)) { + directExtDeps.put(dep.getKey(), TopExtensionDependency.builder().setResolvedDependency(dep) + .setTransitive(!directModuleDeps.containsKey(dep.getKey()))); + } else if (dep.isRuntimeExtensionArtifact() && directModuleDeps.containsKey(dep.getKey())) { + directExtDeps.put(dep.getKey(), TopExtensionDependency.builder().setResolvedDependency(dep)); + } + } + + if (directExtDeps.isEmpty()) { + return projectBuilder.build(); + } + + currentCatalog.getExtensions().forEach(e -> { + final ArtifactKey key = e.getArtifact().getKey(); + final TopExtensionDependency.Builder dep = directExtDeps.get(key); + if (dep != null) { + dep.setCatalogMetadata(e); + } + }); + + for (TopExtensionDependency.Builder extBuilder : directExtDeps.values()) { + final List modules = directModuleDeps.getOrDefault(extBuilder.getKey(), + Collections.emptyList()); + final TopExtensionDependency dep = extBuilder.setTransitive(modules.isEmpty()).build(); + projectBuilder.addExtensionDependency(dep); + for (ModuleState.Builder module : modules) { + module.addExtensionDependency(dep); + } + final ExtensionProvider.Builder provider = extProviderBuilders.computeIfAbsent(dep.getProviderKey(), + k -> ExtensionProvider.builder().setOrigin(dep.getOrigin())); + provider.addExtension(dep); + } + + for (ExtensionProvider.Builder builder : extProviderBuilders.values()) { + projectBuilder.addExtensionProvider(builder.build()); + } + + for (ModuleState.Builder builder : projectModuleBuilders.values()) { + projectBuilder.addModule(builder.build()); + } + + return projectBuilder.build(); + } + +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ExtensionMapBuilder.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ExtensionMapBuilder.java new file mode 100644 index 0000000000000..e91c09f14670b --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ExtensionMapBuilder.java @@ -0,0 +1,115 @@ +package io.quarkus.devtools.project.update; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import io.quarkus.devtools.project.state.TopExtensionDependency; +import io.quarkus.maven.dependency.ArtifactKey; +import io.quarkus.registry.catalog.Extension; + +final class ExtensionMapBuilder { + final Map> extensionInfo; + final List list = new ArrayList<>(); + + public ExtensionMapBuilder() { + this.extensionInfo = new LinkedHashMap<>(); + } + + public ExtensionMapBuilder(int size) { + this.extensionInfo = new LinkedHashMap<>(size); + } + + public void add(ExtensionUpdateInfoBuilder e) { + extensionInfo.put(e.currentDep.getArtifact().getArtifactId(), Collections.singletonList(e)); + list.add(e); + } + + public ExtensionUpdateInfoBuilder get(ArtifactKey key) { + final List list = extensionInfo.get(key.getArtifactId()); + if (list == null || list.isEmpty()) { + return null; + } + if (list.size() == 1) { + return list.get(0); + } + for (ExtensionUpdateInfoBuilder e : list) { + final TopExtensionDependency recommendedDep = e.resolveRecommendedDep(); + if (e.currentDep.getKey().equals(key) + || recommendedDep != null && recommendedDep.getKey().equals(key)) { + return e; + } + } + throw new IllegalArgumentException(key + " isn't found in the extension map"); + } + + public Collection values() { + return list; + } + + public int size() { + return extensionInfo.size(); + } + + public boolean isEmpty() { + return extensionInfo.isEmpty(); + } + + public static final class ExtensionUpdateInfoBuilder { + private final TopExtensionDependency currentDep; + private Extension recommendedMetadata; + private TopExtensionDependency recommendedDep; + + private Extension latestMetadata; + + public ExtensionUpdateInfoBuilder(TopExtensionDependency currentDep) { + this.currentDep = currentDep; + } + + public TopExtensionDependency getCurrentDep() { + return currentDep; + } + + public Extension getRecommendedMetadata() { + return recommendedMetadata; + } + + public void setRecommendedMetadata(Extension e) { + this.recommendedMetadata = e; + } + + public ExtensionUpdateInfoBuilder setRecommendedDep(TopExtensionDependency recommendedDep) { + this.recommendedDep = recommendedDep; + return this; + } + + public ExtensionUpdateInfo build() { + return new ExtensionUpdateInfo(currentDep, currentDep.getCatalogMetadata(), resolveRecommendedDep()); + } + + public TopExtensionDependency resolveRecommendedDep() { + if (recommendedDep != null) { + return recommendedDep; + } + return recommendedMetadata == null ? currentDep + : TopExtensionDependency.builder() + .setArtifact(recommendedMetadata.getArtifact()) + .setCatalogMetadata(recommendedMetadata) + .setTransitive(currentDep.isTransitive()) + .build(); + } + + public Extension getLatestMetadata() { + return latestMetadata; + } + + public ExtensionUpdateInfoBuilder setLatestMetadata(Extension latestMetadata) { + this.latestMetadata = latestMetadata; + return this; + } + } + +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ExtensionUpdateInfo.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ExtensionUpdateInfo.java new file mode 100644 index 0000000000000..5d2b530460937 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ExtensionUpdateInfo.java @@ -0,0 +1,33 @@ +package io.quarkus.devtools.project.update; + +import io.quarkus.devtools.project.state.TopExtensionDependency; +import io.quarkus.registry.catalog.Extension; + +public final class ExtensionUpdateInfo { + private final TopExtensionDependency currentDep; + private final Extension recommendedMetadata; + private final TopExtensionDependency recommendedDep; + + public ExtensionUpdateInfo(TopExtensionDependency currentDep, Extension recommendedMetadata, + TopExtensionDependency recommendedDep) { + this.currentDep = currentDep; + this.recommendedMetadata = recommendedMetadata; + this.recommendedDep = recommendedDep; + } + + public TopExtensionDependency getCurrentDep() { + return currentDep; + } + + public Extension getRecommendedMetadata() { + return recommendedMetadata; + } + + public TopExtensionDependency getRecommendedDependency() { + return recommendedDep; + } + + public boolean isUpdateRecommended() { + return recommendedDep != currentDep; + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/PlatformInfo.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/PlatformInfo.java new file mode 100644 index 0000000000000..020b9a94b90aa --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/PlatformInfo.java @@ -0,0 +1,49 @@ +package io.quarkus.devtools.project.update; + +import io.quarkus.devtools.project.state.ExtensionProvider; +import io.quarkus.maven.dependency.ArtifactCoords; + +public class PlatformInfo { + private final ArtifactCoords imported; + private final ArtifactCoords recommended; + + public PlatformInfo(ArtifactCoords imported, ArtifactCoords recommended) { + this.imported = imported; + this.recommended = recommended; + } + + public ArtifactCoords getImported() { + return imported; + } + + public ArtifactCoords getRecommended() { + return recommended; + } + + public boolean isVersionUpdateRecommended() { + return imported != null && recommended != null && !imported.getVersion().equals(recommended.getVersion()); + } + + public String getRecommendedVersion() { + return recommended == null ? null : recommended.getVersion(); + } + + public boolean isImported() { + return imported != null; + } + + public boolean isToBeImported() { + return imported == null && recommended != null; + } + + public ArtifactCoords getRecommendedCoords() { + return recommended == null ? imported : recommended; + } + + public String getRecommendedProviderKey() { + if (recommended != null) { + return ExtensionProvider.key(recommended, true); + } + return ExtensionProvider.key(imported, true); + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectExtensionsUpdateInfo.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectExtensionsUpdateInfo.java new file mode 100644 index 0000000000000..e4cb0c6445605 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectExtensionsUpdateInfo.java @@ -0,0 +1,45 @@ +package io.quarkus.devtools.project.update; + +import java.util.List; +import java.util.Map; + +import io.quarkus.maven.dependency.ArtifactCoords; + +public class ProjectExtensionsUpdateInfo { + final Map> versionedManagedExtensions; + final Map> removedExtensions; + final Map> addedExtensions; + final Map> nonPlatformExtensions; + + public ProjectExtensionsUpdateInfo(Map> versionedManagedExtensions, + Map> removedExtensions, Map> addedExtensions, + Map> nonPlatformExtensionUpdate) { + this.versionedManagedExtensions = versionedManagedExtensions; + this.removedExtensions = removedExtensions; + this.addedExtensions = addedExtensions; + this.nonPlatformExtensions = nonPlatformExtensionUpdate; + } + + public Map> getVersionedManagedExtensions() { + return versionedManagedExtensions; + } + + public Map> getRemovedExtensions() { + return removedExtensions; + } + + public Map> getAddedExtensions() { + return addedExtensions; + } + + public Map> getNonPlatformExtensions() { + return nonPlatformExtensions; + } + + public boolean isEmpty() { + return versionedManagedExtensions.isEmpty() + && removedExtensions.isEmpty() + && addedExtensions.isEmpty() + && nonPlatformExtensions.isEmpty(); + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectPlatformUpdateInfo.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectPlatformUpdateInfo.java new file mode 100644 index 0000000000000..1958ee644329e --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectPlatformUpdateInfo.java @@ -0,0 +1,43 @@ +package io.quarkus.devtools.project.update; + +import java.util.List; +import java.util.Map; + +import io.quarkus.maven.dependency.ArtifactKey; + +public class ProjectPlatformUpdateInfo { + private final Map platformImports; + private final List importVersionUpdates; + private final List newImports; + private final boolean importsToBeRemoved; + private final boolean platformUpdatesAvailable; + + public ProjectPlatformUpdateInfo(Map platformImports, List importVersionUpdates, + List newImports) { + this.platformImports = platformImports; + this.importVersionUpdates = importVersionUpdates; + this.newImports = newImports; + this.importsToBeRemoved = platformImports.values().stream().anyMatch(p -> p.getRecommended() == null); + this.platformUpdatesAvailable = !importVersionUpdates.isEmpty() || !newImports.isEmpty() || importsToBeRemoved; + } + + public boolean isImportsToBeRemoved() { + return importsToBeRemoved; + } + + public boolean isPlatformUpdatesAvailable() { + return platformUpdatesAvailable; + } + + public Map getPlatformImports() { + return platformImports; + } + + public List getImportVersionUpdates() { + return importVersionUpdates; + } + + public List getNewImports() { + return newImports; + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectUpdateInfos.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectUpdateInfos.java new file mode 100644 index 0000000000000..34443d1a17209 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectUpdateInfos.java @@ -0,0 +1,269 @@ +package io.quarkus.devtools.project.update; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.messagewriter.MessageWriter; +import io.quarkus.devtools.project.state.ExtensionProvider; +import io.quarkus.devtools.project.state.ModuleState; +import io.quarkus.devtools.project.state.ProjectState; +import io.quarkus.devtools.project.state.TopExtensionDependency; +import io.quarkus.devtools.project.update.ExtensionMapBuilder.ExtensionUpdateInfoBuilder; +import io.quarkus.maven.dependency.ArtifactCoords; +import io.quarkus.maven.dependency.ArtifactKey; +import io.quarkus.registry.catalog.Extension; +import io.quarkus.registry.catalog.ExtensionCatalog; +import io.quarkus.registry.catalog.ExtensionOrigin; +import io.quarkus.registry.catalog.selection.ExtensionOrigins; +import io.quarkus.registry.catalog.selection.OriginCombination; +import io.quarkus.registry.catalog.selection.OriginPreference; +import io.quarkus.registry.catalog.selection.OriginSelector; + +public final class ProjectUpdateInfos { + + private ProjectUpdateInfos() { + } + + public static ProjectExtensionsUpdateInfo resolveExtensionsUpdateInfo(ProjectState currentState, + ProjectState recommendedState) { + checkProjectState(currentState, recommendedState); + + final ExtensionMapBuilder extensionInfo = new ExtensionMapBuilder(currentState.getExtensions().size()); + for (TopExtensionDependency dep : currentState.getExtensions()) { + extensionInfo.add(new ExtensionUpdateInfoBuilder(dep)); + } + for (TopExtensionDependency dep : recommendedState.getExtensions()) { + final ExtensionUpdateInfoBuilder info = extensionInfo.get(dep.getKey()); + if (info != null) { + info.setRecommendedDep(dep); + } + } + final Map> versionedManagedExtensions = new LinkedHashMap<>(0); + final Map> removedExtensions = new LinkedHashMap<>(0); + final Map> addedExtensions = new LinkedHashMap<>(0); + final Map> nonPlatformExtensionUpdates = new LinkedHashMap<>(); + for (ExtensionUpdateInfoBuilder infoBuilder : extensionInfo.values()) { + final ExtensionUpdateInfo info = infoBuilder.build(); + if (!info.isUpdateRecommended()) { + continue; + } + if (!info.getCurrentDep().getKey().equals(info.getRecommendedDependency().getKey())) { + if (info.getCurrentDep().isPlatformExtension()) { + removedExtensions.computeIfAbsent(info.getCurrentDep().getProviderKey(), k -> new ArrayList<>()) + .add(info.getCurrentDep().getArtifact()); + } else { + nonPlatformExtensionUpdates.computeIfAbsent(info.getCurrentDep().getProviderKey(), k -> new ArrayList<>()) + .add(info); + } + if (info.getRecommendedDependency().isPlatformExtension()) { + addedExtensions.computeIfAbsent(info.getRecommendedDependency().getProviderKey(), k -> new ArrayList<>()) + .add(info.getRecommendedDependency().getArtifact()); + } else { + nonPlatformExtensionUpdates + .computeIfAbsent(info.getRecommendedDependency().getProviderKey(), k -> new ArrayList<>()) + .add(info); + } + } else if (info.getRecommendedDependency().isPlatformExtension()) { + if (info.getCurrentDep().isNonRecommendedVersion()) { + versionedManagedExtensions + .computeIfAbsent(info.getRecommendedDependency().getProviderKey(), k -> new ArrayList<>()) + .add(info); + } + } else if (!info.getCurrentDep().getVersion().equals(info.getRecommendedDependency().getVersion())) { + nonPlatformExtensionUpdates + .computeIfAbsent(info.getRecommendedDependency().getProviderKey(), k -> new ArrayList<>()).add(info); + } + } + return new ProjectExtensionsUpdateInfo(versionedManagedExtensions, removedExtensions, addedExtensions, + nonPlatformExtensionUpdates); + } + + public static ProjectPlatformUpdateInfo resolvePlatformUpdateInfo(ProjectState currentState, + ProjectState recommendedState) { + checkProjectState(currentState, recommendedState); + + final Map platformImports = new LinkedHashMap<>(); + for (ArtifactCoords c : currentState.getPlatformBoms()) { + final PlatformInfo info = new PlatformInfo(c, null); + platformImports.put(c.getKey(), info); + } + List importVersionUpdates = new ArrayList<>(); + List newImports = new ArrayList<>(0); + for (ArtifactCoords c : recommendedState.getPlatformBoms()) { + final PlatformInfo importInfo = platformImports.compute(c.getKey(), (k, v) -> { + if (v == null) { + return new PlatformInfo(null, c); + } + return new PlatformInfo(v.getImported(), c); + }); + if (importInfo.isToBeImported()) { + newImports.add(importInfo); + } else if (importInfo.isVersionUpdateRecommended()) { + importVersionUpdates.add(importInfo); + } + } + return new ProjectPlatformUpdateInfo(platformImports, importVersionUpdates, newImports); + } + + private static void checkProjectState(ProjectState currentState, ProjectState recommendedState) { + if (currentState.getPlatformBoms().isEmpty()) { + throw new IllegalStateException("The project does not import any Quarkus platform BOM"); + } + if (currentState.getExtensions().isEmpty()) { + throw new IllegalStateException("Quarkus extension were not found among the project dependencies"); + } + if (currentState == recommendedState) { + throw new IllegalStateException("The project is up-to-date"); + } + } + + public static ProjectState resolveRecommendedState(ProjectState currentState, ExtensionCatalog recommendedCatalog, + MessageWriter log) { + if (currentState.getPlatformBoms().isEmpty()) { + return currentState; + } + if (currentState.getExtensions().isEmpty()) { + return currentState; + } + + final ExtensionMapBuilder builder = new ExtensionMapBuilder(); + for (TopExtensionDependency dep : currentState.getExtensions()) { + builder.add(new ExtensionUpdateInfoBuilder(dep)); + } + + for (Extension e : recommendedCatalog.getExtensions()) { + final ExtensionUpdateInfoBuilder candidate = builder.get(e.getArtifact().getKey()); + if (candidate != null && candidate.getLatestMetadata() == null) { + // if the latestMetadata has already been initialized, it's already the preferred one + // that could happen if an artifact has relocated + candidate.setLatestMetadata(e); + } + } + + final List unknownExtensions = new ArrayList<>(0); + final List updateCandidates = new ArrayList<>(builder.size()); + final Map updateCandidatesByOrigin = new HashMap<>(); + for (ExtensionUpdateInfoBuilder i : builder.values()) { + if (i.getLatestMetadata() == null) { + unknownExtensions.add(i); + } else { + updateCandidates.add(i.getLatestMetadata()); + for (ExtensionOrigin o : i.getLatestMetadata().getOrigins()) { + updateCandidatesByOrigin.computeIfAbsent(o.getId(), k -> new ExtensionMapBuilder()).add(i); + } + } + } + + if (builder.isEmpty()) { + return currentState; + } + + if (!unknownExtensions.isEmpty()) { + log.warn( + "The configured Quarkus registries did not provide any compatibility information for the following extensions in the context of the currently recommended Quarkus platforms:"); + unknownExtensions.forEach(e -> log.warn(" " + e.getCurrentDep().getArtifact().toCompactCoords())); + } + + final List recommendedOrigins; + try { + recommendedOrigins = getRecommendedOrigins(recommendedCatalog, updateCandidates); + } catch (QuarkusCommandException e) { + log.warn("Failed to find a compatible configuration update for the project"); + return currentState; + } + + int collectedUpdates = 0; + for (ExtensionCatalog recommendedOrigin : recommendedOrigins) { + final ExtensionMapBuilder candidates = updateCandidatesByOrigin.get(recommendedOrigin.getId()); + for (Extension e : recommendedOrigin.getExtensions()) { + final ExtensionUpdateInfoBuilder info = candidates.get(e.getArtifact().getKey()); + if (info != null && info.getRecommendedMetadata() == null) { + info.setRecommendedMetadata(e); + if (++collectedUpdates == updateCandidates.size()) { + break; + } + } + } + } + + final ProjectState.Builder stateBuilder = ProjectState.builder(); + for (ExtensionCatalog c : recommendedOrigins) { + if (c.isPlatform()) { + stateBuilder.addPlatformBom(c.getBom()); + } + } + + final Map extProviders = new LinkedHashMap<>(recommendedOrigins.size()); + for (ExtensionUpdateInfoBuilder info : builder.values()) { + final TopExtensionDependency ext = info.resolveRecommendedDep(); + stateBuilder.addExtensionDependency(ext); + extProviders.computeIfAbsent(ext.getProviderKey(), k -> ExtensionProvider.builder().setOrigin(ext.getOrigin())) + .addExtension(ext); + } + + extProviders.values().forEach(b -> stateBuilder.addExtensionProvider(b.build())); + + for (ModuleState module : currentState.getModules()) { + final ModuleState.Builder moduleBuilder = ModuleState.builder() + .setMainModule(module.isMain()) + .setWorkspaceModule(module.getWorkspaceModule()); + for (TopExtensionDependency dep : module.getExtensions()) { + final TopExtensionDependency recommendedDep = builder.get(dep.getKey()).resolveRecommendedDep(); + moduleBuilder.addExtensionDependency(recommendedDep); + final ExtensionOrigin origin = recommendedDep.getOrigin(); + if (origin != null && origin.isPlatform()) { + moduleBuilder.addPlatformBom(origin.getBom()); + } + } + stateBuilder.addModule(moduleBuilder.build()); + } + + return stateBuilder.build(); + } + + private static List getRecommendedOrigins(ExtensionCatalog extensionCatalog, List extensions) + throws QuarkusCommandException { + final List extOrigins = new ArrayList<>(extensions.size()); + for (Extension e : extensions) { + addOrigins(extOrigins, e); + } + + final OriginCombination recommendedCombination = OriginSelector.of(extOrigins).calculateRecommendedCombination(); + if (recommendedCombination == null) { + final StringBuilder buf = new StringBuilder(); + buf.append("Failed to determine a compatible Quarkus version for the requested extensions: "); + buf.append(extensions.get(0).getArtifact().getKey().toGacString()); + for (int i = 1; i < extensions.size(); ++i) { + buf.append(", ").append(extensions.get(i).getArtifact().getKey().toGacString()); + } + throw new QuarkusCommandException(buf.toString()); + } + return recommendedCombination.getUniqueSortedOrigins().stream().map(o -> o.getCatalog()).collect(Collectors.toList()); + } + + private static void addOrigins(final List extOrigins, Extension e) { + ExtensionOrigins.Builder eoBuilder = null; + for (ExtensionOrigin o : e.getOrigins()) { + if (!(o instanceof ExtensionCatalog)) { + continue; + } + final ExtensionCatalog c = (ExtensionCatalog) o; + final OriginPreference op = (OriginPreference) c.getMetadata().get("origin-preference"); + if (op == null) { + continue; + } + if (eoBuilder == null) { + eoBuilder = ExtensionOrigins.builder(e.getArtifact().getKey()); + } + eoBuilder.addOrigin(c, op); + } + if (eoBuilder != null) { + extOrigins.add(eoBuilder.build()); + } + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateCommand.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java similarity index 98% rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateCommand.java rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java index 9709f184cb539..41d4c6ccf6c17 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateCommand.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java @@ -1,6 +1,6 @@ -package io.quarkus.devtools.project.update; +package io.quarkus.devtools.project.update.rewrite; -import static io.quarkus.devtools.project.update.QuarkusUpdateRecipe.RECIPE_IO_QUARKUS_OPENREWRITE_QUARKUS; +import static io.quarkus.devtools.project.update.rewrite.QuarkusUpdateRecipe.RECIPE_IO_QUARKUS_OPENREWRITE_QUARKUS; import java.io.BufferedReader; import java.io.File; diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateException.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateException.java similarity index 85% rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateException.java rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateException.java index 6bceefbf0b9cb..755c396fdbe39 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateException.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateException.java @@ -1,4 +1,4 @@ -package io.quarkus.devtools.project.update; +package io.quarkus.devtools.project.update.rewrite; public class QuarkusUpdateException extends Exception { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateRecipe.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateRecipe.java similarity index 97% rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateRecipe.java rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateRecipe.java index 6df006981e367..2d6339b98ae9d 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateRecipe.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateRecipe.java @@ -1,4 +1,4 @@ -package io.quarkus.devtools.project.update; +package io.quarkus.devtools.project.update.rewrite; import java.util.ArrayList; import java.util.List; diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateRecipeIO.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateRecipeIO.java similarity index 97% rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateRecipeIO.java rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateRecipeIO.java index 964e0e44822ba..c6f6485ddb0d0 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateRecipeIO.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateRecipeIO.java @@ -1,4 +1,4 @@ -package io.quarkus.devtools.project.update; +package io.quarkus.devtools.project.update.rewrite; import java.io.IOException; import java.nio.file.Files; diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdates.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdates.java similarity index 89% rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdates.java rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdates.java index 46140c32f074a..3520a54cadeda 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdates.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdates.java @@ -1,4 +1,4 @@ -package io.quarkus.devtools.project.update; +package io.quarkus.devtools.project.update.rewrite; import java.io.IOException; import java.nio.file.Path; @@ -6,9 +6,9 @@ import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.devtools.messagewriter.MessageWriter; import io.quarkus.devtools.project.BuildTool; -import io.quarkus.devtools.project.update.QuarkusUpdatesRepository.FetchResult; -import io.quarkus.devtools.project.update.operations.UpdatePropertyOperation; -import io.quarkus.devtools.project.update.operations.UpgradeGradlePluginOperation; +import io.quarkus.devtools.project.update.rewrite.QuarkusUpdatesRepository.FetchResult; +import io.quarkus.devtools.project.update.rewrite.operations.UpdatePropertyOperation; +import io.quarkus.devtools.project.update.rewrite.operations.UpgradeGradlePluginOperation; public final class QuarkusUpdates { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdatesRepository.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepository.java similarity index 99% rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdatesRepository.java rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepository.java index b3f86eab45342..1758eba2497df 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdatesRepository.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepository.java @@ -1,4 +1,4 @@ -package io.quarkus.devtools.project.update; +package io.quarkus.devtools.project.update.rewrite; import java.io.IOException; import java.io.InputStream; diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/RewriteOperation.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/RewriteOperation.java similarity index 88% rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/RewriteOperation.java rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/RewriteOperation.java index 1cd866a5ef55e..7e69d0e413720 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/RewriteOperation.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/RewriteOperation.java @@ -1,4 +1,4 @@ -package io.quarkus.devtools.project.update; +package io.quarkus.devtools.project.update.rewrite; import java.util.Map; diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpdateDependencyVersionOperation.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateDependencyVersionOperation.java similarity index 87% rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpdateDependencyVersionOperation.java rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateDependencyVersionOperation.java index 8f6c63b65e4fd..ea47c75b415c6 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpdateDependencyVersionOperation.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateDependencyVersionOperation.java @@ -1,9 +1,9 @@ -package io.quarkus.devtools.project.update.operations; +package io.quarkus.devtools.project.update.rewrite.operations; import java.util.Map; import io.quarkus.devtools.project.BuildTool; -import io.quarkus.devtools.project.update.RewriteOperation; +import io.quarkus.devtools.project.update.rewrite.RewriteOperation; public class UpdateDependencyVersionOperation implements RewriteOperation { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpdateManagedDependencyVersionOperation.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateManagedDependencyVersionOperation.java similarity index 89% rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpdateManagedDependencyVersionOperation.java rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateManagedDependencyVersionOperation.java index 4959ee7d2e832..172e077f0a1a2 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpdateManagedDependencyVersionOperation.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateManagedDependencyVersionOperation.java @@ -1,9 +1,9 @@ -package io.quarkus.devtools.project.update.operations; +package io.quarkus.devtools.project.update.rewrite.operations; import java.util.Map; import io.quarkus.devtools.project.BuildTool; -import io.quarkus.devtools.project.update.RewriteOperation; +import io.quarkus.devtools.project.update.rewrite.RewriteOperation; public class UpdateManagedDependencyVersionOperation implements RewriteOperation { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpdatePropertyOperation.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdatePropertyOperation.java similarity index 86% rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpdatePropertyOperation.java rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdatePropertyOperation.java index 464f625ec42f3..0929c5a8780ae 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpdatePropertyOperation.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdatePropertyOperation.java @@ -1,9 +1,9 @@ -package io.quarkus.devtools.project.update.operations; +package io.quarkus.devtools.project.update.rewrite.operations; import java.util.Map; import io.quarkus.devtools.project.BuildTool; -import io.quarkus.devtools.project.update.RewriteOperation; +import io.quarkus.devtools.project.update.rewrite.RewriteOperation; public class UpdatePropertyOperation implements RewriteOperation { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpgradeGradlePluginOperation.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpgradeGradlePluginOperation.java similarity index 86% rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpgradeGradlePluginOperation.java rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpgradeGradlePluginOperation.java index 2ad0899601f87..2fdcbb2a5d71f 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpgradeGradlePluginOperation.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpgradeGradlePluginOperation.java @@ -1,9 +1,9 @@ -package io.quarkus.devtools.project.update.operations; +package io.quarkus.devtools.project.update.rewrite.operations; import java.util.Map; import io.quarkus.devtools.project.BuildTool; -import io.quarkus.devtools.project.update.RewriteOperation; +import io.quarkus.devtools.project.update.rewrite.RewriteOperation; public class UpgradeGradlePluginOperation implements RewriteOperation { diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/QuarkusUpdateRecipeIOTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateRecipeIOTest.java similarity index 86% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/QuarkusUpdateRecipeIOTest.java rename to independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateRecipeIOTest.java index 54b7b59986984..1c231d8cc9dc9 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/QuarkusUpdateRecipeIOTest.java +++ b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateRecipeIOTest.java @@ -1,4 +1,4 @@ -package io.quarkus.devtools.project.update; +package io.quarkus.devtools.project.update.rewrite; import static org.assertj.core.api.Assertions.assertThat; @@ -6,8 +6,8 @@ import org.junit.jupiter.api.Test; -import io.quarkus.devtools.project.update.operations.UpdateManagedDependencyVersionOperation; -import io.quarkus.devtools.project.update.operations.UpdatePropertyOperation; +import io.quarkus.devtools.project.update.rewrite.operations.UpdateManagedDependencyVersionOperation; +import io.quarkus.devtools.project.update.rewrite.operations.UpdatePropertyOperation; class QuarkusUpdateRecipeIOTest { diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/QuarkusUpdatesRepositoryTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepositoryTest.java similarity index 79% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/QuarkusUpdatesRepositoryTest.java rename to independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepositoryTest.java index 5e4dd6d4935d6..9915e9bdf0fda 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/QuarkusUpdatesRepositoryTest.java +++ b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepositoryTest.java @@ -1,6 +1,6 @@ -package io.quarkus.devtools.project.update; +package io.quarkus.devtools.project.update.rewrite; -import static io.quarkus.devtools.project.update.QuarkusUpdatesRepository.shouldApplyRecipe; +import static io.quarkus.devtools.project.update.rewrite.QuarkusUpdatesRepository.shouldApplyRecipe; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.params.ParameterizedTest; From 797db876def2286f49ae924f757fd900fdb0c7f7 Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Fri, 16 Jun 2023 16:57:11 +0200 Subject: [PATCH 08/22] Check for min java version when updating (partial) (cherry picked from commit 848a6f25e513c209e6a14e9caf3609f44b853ccf) --- .../main/java/io/quarkus/cli/CreateApp.java | 2 +- .../main/java/io/quarkus/cli/CreateCli.java | 2 +- .../quarkus/cli/create/BaseCreateCommand.java | 4 +- .../cli/create/TargetLanguageGroup.java | 10 +- .../gradle/tasks/QuarkusPlatformTask.java | 16 ++- .../io/quarkus/maven/CreateJBangMojo.java | 3 +- .../io/quarkus/maven/CreateProjectMojo.java | 3 +- .../quarkus/maven/QuarkusProjectMojoBase.java | 3 +- ...rkusJBangCodestartProjectInputBuilder.java | 4 +- .../devtools/commands/CreateJBangProject.java | 3 +- .../devtools/commands/CreateProject.java | 3 +- .../commands/CreateProjectHelper.java | 51 +-------- .../handlers/CreateProjectCommandHandler.java | 4 +- .../handlers/UpdateProjectCommandHandler.java | 18 +++- .../quarkus/devtools/project/JavaVersion.java | 101 ++++++++++++++++++ .../devtools/project/QuarkusProject.java | 17 ++- .../project/QuarkusProjectHelper.java | 19 ++-- .../{commands => project}/SourceType.java | 2 +- .../buildfile/MavenProjectBuildFile.java | 17 ++- .../update/ProjectExtensionsUpdateInfo.java | 10 ++ .../update/rewrite/QuarkusUpdates.java | 19 ++-- .../JavaVersionTest.java} | 14 +-- .../gradle/QuarkusPluginFunctionalTest.java | 4 +- 23 files changed, 224 insertions(+), 105 deletions(-) create mode 100644 independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java rename independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/{commands => project}/SourceType.java (94%) rename independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/{commands/CreateProjectHelperTest.java => project/JavaVersionTest.java} (75%) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java index b2f307e9aff95..7630ef8928d3b 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java @@ -12,7 +12,7 @@ import io.quarkus.cli.create.TargetGAVGroup; import io.quarkus.cli.create.TargetLanguageGroup; import io.quarkus.devtools.commands.CreateProject.CreateProjectKey; -import io.quarkus.devtools.commands.SourceType; +import io.quarkus.devtools.project.SourceType; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.handlers.CreateJBangProjectCommandHandler; import io.quarkus.devtools.commands.handlers.CreateProjectCommandHandler; diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java index b240342b504d5..d17c38360d252 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java @@ -12,7 +12,7 @@ import io.quarkus.cli.create.TargetGAVGroup; import io.quarkus.cli.create.TargetLanguageGroup; import io.quarkus.devtools.commands.CreateProject.CreateProjectKey; -import io.quarkus.devtools.commands.SourceType; +import io.quarkus.devtools.project.SourceType; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.handlers.CreateJBangProjectCommandHandler; import io.quarkus.devtools.commands.handlers.CreateProjectCommandHandler; diff --git a/devtools/cli/src/main/java/io/quarkus/cli/create/BaseCreateCommand.java b/devtools/cli/src/main/java/io/quarkus/cli/create/BaseCreateCommand.java index ef87e60871c70..dc58dcbb4a7a4 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/create/BaseCreateCommand.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/create/BaseCreateCommand.java @@ -1,6 +1,6 @@ package io.quarkus.cli.create; -import static io.quarkus.devtools.commands.CreateProjectHelper.computeJavaVersion; +import static io.quarkus.devtools.project.JavaVersion.computeJavaVersion; import java.nio.file.Path; import java.util.Collection; @@ -17,7 +17,7 @@ import io.quarkus.cli.registry.ToggleRegistryClientMixin; import io.quarkus.devtools.commands.CreateProject.CreateProjectKey; import io.quarkus.devtools.commands.CreateProjectHelper; -import io.quarkus.devtools.commands.SourceType; +import io.quarkus.devtools.project.SourceType; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.QuarkusProject; diff --git a/devtools/cli/src/main/java/io/quarkus/cli/create/TargetLanguageGroup.java b/devtools/cli/src/main/java/io/quarkus/cli/create/TargetLanguageGroup.java index c889f156f8f0a..69f0a830d6ccd 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/create/TargetLanguageGroup.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/create/TargetLanguageGroup.java @@ -5,9 +5,9 @@ import java.util.stream.Collectors; import io.quarkus.cli.common.OutputOptionMixin; -import io.quarkus.devtools.commands.CreateProjectHelper; -import io.quarkus.devtools.commands.SourceType; +import io.quarkus.devtools.project.SourceType; import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.JavaVersion; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.ParameterException; @@ -17,13 +17,13 @@ public class TargetLanguageGroup { static class VersionCandidates extends ArrayList { VersionCandidates() { - super(CreateProjectHelper.JAVA_VERSIONS_LTS.stream().map(String::valueOf).collect(Collectors.toList())); + super(JavaVersion.JAVA_VERSIONS_LTS.stream().map(String::valueOf).collect(Collectors.toList())); } } @CommandLine.Option(names = { - "--java" }, description = "Target Java version.\n Valid values: ${COMPLETION-CANDIDATES}", completionCandidates = VersionCandidates.class, defaultValue = CreateProjectHelper.DETECT_JAVA_RUNTIME_VERSION) - String javaVersion = CreateProjectHelper.DETECT_JAVA_RUNTIME_VERSION; + "--java" }, description = "Target Java version.\n Valid values: ${COMPLETION-CANDIDATES}", completionCandidates = VersionCandidates.class, defaultValue = JavaVersion.DETECT_JAVA_RUNTIME_VERSION) + String javaVersion = JavaVersion.DETECT_JAVA_RUNTIME_VERSION; @CommandLine.Option(names = { "--kotlin" }, description = "Use Kotlin") boolean kotlin = false; diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java index 5bc34c46318bd..f71a6eadaeb0a 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java @@ -15,9 +15,12 @@ import org.gradle.api.GradleException; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; +import org.gradle.api.tasks.TaskCollection; +import org.gradle.api.tasks.compile.JavaCompile; import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.devtools.messagewriter.MessageWriter; +import io.quarkus.devtools.project.JavaVersion; import io.quarkus.devtools.project.QuarkusProject; import io.quarkus.devtools.project.QuarkusProjectHelper; import io.quarkus.devtools.project.buildfile.BuildFile; @@ -118,7 +121,6 @@ protected String quarkusCoreVersion() { } protected QuarkusProject getQuarkusProject(boolean limitExtensionsToImportedPlatforms) { - final GradleMessageWriter log = messageWriter(); final ExtensionCatalog catalog = extensionsCatalog(limitExtensionsToImportedPlatforms, log); @@ -136,7 +138,17 @@ protected QuarkusProject getQuarkusProject(boolean limitExtensionsToImportedPlat throw new GradleException( "Mixed DSL is not supported. Both build and settings file need to use either Kotlin or Groovy DSL"); } - return QuarkusProjectHelper.getProject(getProject().getProjectDir().toPath(), catalog, buildFile, log); + final JavaVersion javaVersion = resolveProjectJavaVersion(); + return QuarkusProjectHelper.getProject(getProject().getProjectDir().toPath(), catalog, buildFile, javaVersion, log); + } + + private JavaVersion resolveProjectJavaVersion() { + TaskCollection compileTasks = getProject().getTasks().withType(JavaCompile.class); + if (compileTasks.isEmpty()) { + return JavaVersion.NA; + } + final JavaCompile task = compileTasks.iterator().next(); + return new JavaVersion(task.getTargetCompatibility()); } protected GradleMessageWriter messageWriter() { diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateJBangMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateJBangMojo.java index 0cf1c27dcf8d0..2a8e407bdd1f5 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateJBangMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateJBangMojo.java @@ -26,6 +26,7 @@ import io.quarkus.devtools.commands.data.QuarkusCommandException; import io.quarkus.devtools.messagewriter.MessageWriter; import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.JavaVersion; import io.quarkus.devtools.project.QuarkusProject; import io.quarkus.devtools.project.QuarkusProjectHelper; import io.quarkus.maven.utilities.MojoUtils; @@ -117,7 +118,7 @@ public void execute() throws MojoExecutionException { .artifactResolver(mvn) .build(); final CreateJBangProject createJBangProject = new CreateJBangProject(QuarkusProject.of(projectDirPath, catalog, - codestartsResourceLoader, log, BuildTool.MAVEN)) + codestartsResourceLoader, log, BuildTool.MAVEN, new JavaVersion(javaVersion))) .extensions(extensions) .javaVersion(javaVersion) .setValue(NO_JBANG_WRAPPER, noJBangWrapper); diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java index eef16672f6636..15fd70e323906 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java @@ -37,6 +37,7 @@ import io.quarkus.devtools.commands.CreateProjectHelper; import io.quarkus.devtools.messagewriter.MessageWriter; import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.JavaVersion; import io.quarkus.devtools.project.QuarkusProject; import io.quarkus.devtools.project.QuarkusProjectHelper; import io.quarkus.maven.components.MavenVersionEnforcer; @@ -297,7 +298,7 @@ public void execute() throws MojoExecutionException { .artifactResolver(mvn) .build(); QuarkusProject newProject = QuarkusProject.of(projectDirPath, catalog, - codestartsResourceLoader, log, buildToolEnum); + codestartsResourceLoader, log, buildToolEnum, new JavaVersion(javaVersion, javaVersion)); final CreateProject createProject = new CreateProject(newProject) .groupId(projectGroupId) .artifactId(projectArtifactId) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectMojoBase.java b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectMojoBase.java index 2e5ed5a3ae2dc..16043a4d5aa76 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectMojoBase.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectMojoBase.java @@ -29,6 +29,7 @@ import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.devtools.messagewriter.MessageWriter; import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.JavaVersion; import io.quarkus.devtools.project.QuarkusProject; import io.quarkus.devtools.project.QuarkusProjectHelper; import io.quarkus.devtools.project.buildfile.MavenProjectBuildFile; @@ -106,7 +107,7 @@ public void execute() throws MojoExecutionException { final List codestartsResourceLoader = getCodestartResourceLoaders(resolveExtensionCatalog()); quarkusProject = QuarkusProject.of(baseDir(), resolveExtensionCatalog(), codestartsResourceLoader, - log, buildTool); + log, buildTool, JavaVersion.NA); } doExecute(quarkusProject, getMessageWriter()); diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartProjectInputBuilder.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartProjectInputBuilder.java index 1bd1336c59300..9295b72897373 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartProjectInputBuilder.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartProjectInputBuilder.java @@ -9,8 +9,8 @@ import io.quarkus.devtools.codestarts.CodestartProjectInputBuilder; import io.quarkus.devtools.codestarts.DataKey; import io.quarkus.devtools.codestarts.utils.NestedMaps; -import io.quarkus.devtools.commands.CreateProjectHelper; import io.quarkus.devtools.messagewriter.MessageWriter; +import io.quarkus.devtools.project.JavaVersion; import io.quarkus.devtools.project.extensions.Extensions; import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactKey; @@ -86,7 +86,7 @@ public boolean noJBangWrapper() { public QuarkusJBangCodestartProjectInput build() { if (!this.containsData("java")) { this.addData(NestedMaps - .unflatten(Map.of("java.version", String.valueOf(CreateProjectHelper.determineBestJavaLtsVersion())))); + .unflatten(Map.of("java.version", String.valueOf(JavaVersion.determineBestJavaLtsVersion())))); } return new QuarkusJBangCodestartProjectInput(this); } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateJBangProject.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateJBangProject.java index 0d381591770e1..8d619685532d7 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateJBangProject.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateJBangProject.java @@ -1,6 +1,6 @@ package io.quarkus.devtools.commands; -import static io.quarkus.devtools.commands.CreateProjectHelper.computeJavaVersion; +import static io.quarkus.devtools.project.JavaVersion.computeJavaVersion; import static java.util.Objects.requireNonNull; import java.util.HashMap; @@ -14,6 +14,7 @@ import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; import io.quarkus.devtools.commands.handlers.CreateJBangProjectCommandHandler; import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.devtools.project.SourceType; public class CreateJBangProject { public interface CreateJBangProjectKey { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java index 72a53d5c95ce6..b26d321e9fed6 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java @@ -1,9 +1,9 @@ package io.quarkus.devtools.commands; import static io.quarkus.devtools.commands.CreateProject.CreateProjectKey.*; -import static io.quarkus.devtools.commands.CreateProjectHelper.computeJavaVersion; import static io.quarkus.devtools.commands.handlers.CreateProjectCodestartDataConverter.PlatformPropertiesKey.QUARKUS_GRADLE_PLUGIN_VERSION; import static io.quarkus.devtools.commands.handlers.CreateProjectCodestartDataConverter.PlatformPropertiesKey.QUARKUS_MAVEN_PLUGIN_VERSION; +import static io.quarkus.devtools.project.JavaVersion.computeJavaVersion; import static java.util.Objects.requireNonNull; import java.util.Collections; @@ -20,6 +20,7 @@ import io.quarkus.devtools.commands.handlers.CreateProjectCommandHandler; import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.devtools.project.SourceType; import io.quarkus.platform.tools.ToolsUtils; /** diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProjectHelper.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProjectHelper.java index 3796b66238c5c..015eef38812f0 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProjectHelper.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProjectHelper.java @@ -11,10 +11,6 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.lang.model.SourceVersion; @@ -25,6 +21,7 @@ import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.devtools.commands.CreateProject.CreateProjectKey; +import io.quarkus.devtools.project.SourceType; import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.paths.PathTree; import io.quarkus.registry.catalog.Extension; @@ -32,13 +29,6 @@ public class CreateProjectHelper { - // ordering is important here, so let's keep them ordered - public static final SortedSet JAVA_VERSIONS_LTS = new TreeSet<>(List.of(11, 17)); - public static final int DEFAULT_JAVA_VERSION = 11; - private static final int MAX_LTS_SUPPORTED_BY_KOTLIN = 17; - public static final String DETECT_JAVA_RUNTIME_VERSION = "<>"; - private static final Pattern JAVA_VERSION_PATTERN = Pattern.compile("(\\d+)(?:\\..*)?"); - public static final String DEFAULT_GROUP_ID = "org.acme"; public static final String DEFAULT_ARTIFACT_ID = "code-with-quarkus"; public static final String DEFAULT_VERSION = "1.0.0-SNAPSHOT"; @@ -177,45 +167,6 @@ public static Path createOutputDirectory(String targetDirectory) { return outputPath; } - public static String computeJavaVersion(SourceType sourceType, String inputJavaVersion) { - Integer javaFeatureVersionTarget = null; - - if (inputJavaVersion != null && !DETECT_JAVA_RUNTIME_VERSION.equals(inputJavaVersion)) { - // Probably too much as we should push only the feature version but let's be as thorough as we used to be - Matcher matcher = JAVA_VERSION_PATTERN.matcher(inputJavaVersion); - if (matcher.matches()) { - javaFeatureVersionTarget = Integer.valueOf(matcher.group(1)); - } - } - - if (javaFeatureVersionTarget == null) { - javaFeatureVersionTarget = Runtime.version().feature(); - } - - int bestJavaLtsVersion = determineBestJavaLtsVersion(javaFeatureVersionTarget); - - if (SourceType.KOTLIN.equals(sourceType) - && bestJavaLtsVersion > MAX_LTS_SUPPORTED_BY_KOTLIN) { - bestJavaLtsVersion = MAX_LTS_SUPPORTED_BY_KOTLIN; - } - return String.valueOf(bestJavaLtsVersion); - } - - public static int determineBestJavaLtsVersion() { - return determineBestJavaLtsVersion(Runtime.version().feature()); - } - - public static int determineBestJavaLtsVersion(int runtimeVersion) { - int bestLtsVersion = DEFAULT_JAVA_VERSION; - for (int ltsVersion : JAVA_VERSIONS_LTS) { - if (ltsVersion > runtimeVersion) { - break; - } - bestLtsVersion = ltsVersion; - } - return bestLtsVersion; - } - public static Set sanitizeExtensions(Set extensions) { if (extensions == null) { return extensions = Set.of(); diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java index 33ed3c0048ca3..0711461f91d0f 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java @@ -20,13 +20,13 @@ import io.quarkus.devtools.codestarts.CodestartType; import io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartCatalog; import io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartProjectInput; -import io.quarkus.devtools.commands.CreateProjectHelper; import io.quarkus.devtools.commands.data.QuarkusCommandException; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; import io.quarkus.devtools.commands.handlers.CreateProjectCodestartDataConverter.CatalogKey; import io.quarkus.devtools.messagewriter.MessageIcons; import io.quarkus.devtools.messagewriter.MessageWriter; +import io.quarkus.devtools.project.JavaVersion; import io.quarkus.devtools.project.extensions.Extensions; import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.platform.tools.ToolsUtils; @@ -232,7 +232,7 @@ private static void addOrigins(final List extOrigins, Extensio private void checkMinimumJavaVersion(String javaVersionString, List extensions) throws QuarkusCommandException { final List incompatibleExtensions = new ArrayList<>(); - final int javaVersion = javaVersionString == null ? CreateProjectHelper.DEFAULT_JAVA_VERSION + final int javaVersion = javaVersionString == null ? JavaVersion.DEFAULT_JAVA_VERSION : Integer.parseInt(javaVersionString); for (Extension extension : extensions) { Integer extMinJavaVersion = getMinimumJavaVersion(extension); diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java index 187733c7e2abd..6b4eb609af4d5 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java @@ -43,10 +43,10 @@ public class UpdateProjectCommandHandler implements QuarkusCommandHandler { @Override public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException { + invocation.log().info("Detected project Java version: %s", invocation.getQuarkusProject().getJavaVersion()); final ApplicationModel appModel = invocation.getValue(UpdateProject.APP_MODEL); final ExtensionCatalog targetCatalog = invocation.getValue(UpdateProject.TARGET_CATALOG); final String targetPlatformVersion = invocation.getValue(UpdateProject.TARGET_PLATFORM_VERSION); - final boolean perModule = invocation.getValue(UpdateProject.PER_MODULE, false); final ProjectState currentState = resolveProjectState(appModel, invocation.getQuarkusProject().getExtensionsCatalog()); @@ -68,7 +68,8 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws currentState, recommendedState); - logUpdates(currentState, recommendedState, platformUpdateInfo, extensionsUpdateInfo, false, perModule, + logUpdates(invocation.getQuarkusProject(), currentState, recommendedState, platformUpdateInfo, extensionsUpdateInfo, + false, perModule, quarkusProject.log()); final boolean noRewrite = invocation.getValue(UpdateProject.NO_REWRITE, false); @@ -80,7 +81,8 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws buildTool, projectQuarkusPlatformBom.getVersion(), targetPlatformVersion, - kotlinVersion); + kotlinVersion, + extensionsUpdateInfo.getMinJavaVersion()); Path recipe = null; try { recipe = Files.createTempFile("quarkus-project-recipe-", ".yaml"); @@ -131,7 +133,7 @@ private static ArtifactCoords getProjectQuarkusPlatformBOM(ProjectState currentS return null; } - private static void logUpdates(ProjectState currentState, ProjectState recommendedState, + private static void logUpdates(QuarkusProject project, ProjectState currentState, ProjectState recommendedState, ProjectPlatformUpdateInfo platformUpdateInfo, ProjectExtensionsUpdateInfo extensionsUpdateInfo, boolean recommendState, boolean perModule, MessageWriter log) { @@ -152,7 +154,6 @@ private static void logUpdates(ProjectState currentState, ProjectState recommend ProjectInfoCommandHandler.logState(recommendedState, perModule, false, log); return; } - if (platformUpdateInfo.isPlatformUpdatesAvailable()) { log.info("Recommended Quarkus platform BOM updates:"); if (!platformUpdateInfo.getImportVersionUpdates().isEmpty()) { @@ -236,6 +237,13 @@ private static void logUpdates(ProjectState currentState, ProjectState recommend log.info(""); } } + if (extensionsUpdateInfo.getMinJavaVersion().isPresent()) { + final int extensionsMinJavaVersion = extensionsUpdateInfo.getMinJavaVersion().getAsInt(); + if (extensionsMinJavaVersion > project.getJavaVersion().getAsInt()) { + log.warn("We detected that some of the updated extensions require an update of the Java version to: ", + extensionsMinJavaVersion); + } + } } @SuppressWarnings({ "rawtypes", "unchecked" }) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java new file mode 100644 index 0000000000000..edff9ab037a05 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java @@ -0,0 +1,101 @@ +package io.quarkus.devtools.project; + +import java.util.List; +import java.util.Objects; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class JavaVersion { + + public static final JavaVersion NA = new JavaVersion(); + + private final String version; + + private JavaVersion() { + this(null); + } + + public JavaVersion(String version) { + this.version = version; + } + + public boolean isEmpty() { + return version == null; + } + + public String getVersion() { + return version; + } + + public int getAsInt() { + return Integer.parseInt(version); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + JavaVersion that = (JavaVersion) o; + return Objects.equals(version, that.version); + } + + @Override + public int hashCode() { + return Objects.hash(version); + } + + @Override + public String toString() { + return version; + } + + // ordering is important here, so let's keep them ordered + public static final SortedSet JAVA_VERSIONS_LTS = new TreeSet<>(List.of(11, 17)); + public static final int DEFAULT_JAVA_VERSION = 11; + public static final int MAX_LTS_SUPPORTED_BY_KOTLIN = 17; + public static final String DETECT_JAVA_RUNTIME_VERSION = "<>"; + public static final Pattern JAVA_VERSION_PATTERN = Pattern.compile("(\\d+)(?:\\..*)?"); + + public static int determineBestJavaLtsVersion() { + return determineBestJavaLtsVersion(Runtime.version().feature()); + } + + public static int determineBestJavaLtsVersion(int runtimeVersion) { + int bestLtsVersion = DEFAULT_JAVA_VERSION; + for (int ltsVersion : JAVA_VERSIONS_LTS) { + if (ltsVersion > runtimeVersion) { + break; + } + bestLtsVersion = ltsVersion; + } + return bestLtsVersion; + } + + public static String computeJavaVersion(SourceType sourceType, String inputJavaVersion) { + Integer javaFeatureVersionTarget = null; + + if (inputJavaVersion != null && !DETECT_JAVA_RUNTIME_VERSION.equals(inputJavaVersion)) { + // Probably too much as we should push only the feature version but let's be as thorough as we used to be + Matcher matcher = JAVA_VERSION_PATTERN.matcher(inputJavaVersion); + if (matcher.matches()) { + javaFeatureVersionTarget = Integer.valueOf(matcher.group(1)); + } + } + + if (javaFeatureVersionTarget == null) { + javaFeatureVersionTarget = Runtime.version().feature(); + } + + int bestJavaLtsVersion = determineBestJavaLtsVersion(javaFeatureVersionTarget); + + if (SourceType.KOTLIN.equals(sourceType) + && bestJavaLtsVersion > MAX_LTS_SUPPORTED_BY_KOTLIN) { + bestJavaLtsVersion = MAX_LTS_SUPPORTED_BY_KOTLIN; + } + return String.valueOf(bestJavaLtsVersion); + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProject.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProject.java index 153aa5b1d6329..73a7da4518648 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProject.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProject.java @@ -18,27 +18,30 @@ public final class QuarkusProject { private final ExtensionManager extensionManager; private final MessageWriter log; + private final JavaVersion javaVersion; + private QuarkusProject(Path projectDirPath, ExtensionCatalog catalog, List codestartResourceLoaders, MessageWriter log, - ExtensionManager extensionManager) { + ExtensionManager extensionManager, JavaVersion javaVersion) { this.projectDirPath = requireNonNull(projectDirPath, "projectDirPath is required"); this.catalog = requireNonNull(catalog, "catalog is required"); this.codestartResourceLoaders = requireNonNull(codestartResourceLoaders, "codestartResourceLoaders is required"); this.extensionManager = requireNonNull(extensionManager, "extensionManager is required"); this.log = (log == null ? MessageWriter.info() : log); + this.javaVersion = javaVersion; } public static QuarkusProject of(final Path projectDirPath, final ExtensionCatalog catalog, final List codestartResourceLoaders, final MessageWriter log, - final ExtensionManager extensionManager) { - return new QuarkusProject(projectDirPath, catalog, codestartResourceLoaders, log, extensionManager); + final ExtensionManager extensionManager, JavaVersion javaVersion) { + return new QuarkusProject(projectDirPath, catalog, codestartResourceLoaders, log, extensionManager, javaVersion); } public static QuarkusProject of(final Path projectDirPath, ExtensionCatalog catalog, final List codestartsResourceLoader, - final MessageWriter log, final BuildTool buildTool) { + final MessageWriter log, final BuildTool buildTool, JavaVersion javaVersion) { return new QuarkusProject(projectDirPath, catalog, codestartsResourceLoader, log, - buildTool.createExtensionManager(projectDirPath, catalog)); + buildTool.createExtensionManager(projectDirPath, catalog), javaVersion); } public Path getProjectDirPath() { @@ -49,6 +52,10 @@ public BuildTool getBuildTool() { return extensionManager.getBuildTool(); } + public JavaVersion getJavaVersion() { + return javaVersion; + } + public ExtensionManager getExtensionManager() { return extensionManager; } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java index 09502599e3ee4..d559cead7f548 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java @@ -23,6 +23,7 @@ public class QuarkusProjectHelper { private static ExtensionCatalogResolver catalogResolver; private static boolean registryClientEnabled; + static { initRegistryClientEnabled(); } @@ -66,7 +67,8 @@ public static QuarkusProject getProject(Path projectDir, BuildTool buildTool, St // TODO remove this method once the default registry becomes available return QuarkusProjectHelper.getProject(projectDir, getExtensionCatalog(quarkusVersion), - buildTool); + buildTool, + JavaVersion.NA); } @Deprecated @@ -100,21 +102,23 @@ public static QuarkusProject getProject(Path projectDir, BuildTool buildTool) { throw new RuntimeException("Failed to resolve the Quarkus extension catalog", e); } - return getProject(projectDir, catalog, buildTool, messageWriter()); + return getProject(projectDir, catalog, buildTool, JavaVersion.NA, messageWriter()); } - public static QuarkusProject getProject(Path projectDir, ExtensionCatalog catalog, BuildTool buildTool) { - return getProject(projectDir, catalog, buildTool, messageWriter()); + public static QuarkusProject getProject(Path projectDir, ExtensionCatalog catalog, BuildTool buildTool, + JavaVersion javaVersion) { + return getProject(projectDir, catalog, buildTool, javaVersion, messageWriter()); } public static QuarkusProject getProject(Path projectDir, ExtensionCatalog catalog, BuildTool buildTool, + JavaVersion javaVersion, MessageWriter log) { return QuarkusProject.of(projectDir, catalog, getCodestartResourceLoaders(catalog), - log, buildTool); + log, buildTool, javaVersion); } public static QuarkusProject getProject(Path projectDir, ExtensionManager extManager) throws RegistryResolutionException { - return getProject(projectDir, resolveExtensionCatalog(), extManager, messageWriter()); + return getProject(projectDir, resolveExtensionCatalog(), extManager, JavaVersion.NA, messageWriter()); } public static ExtensionCatalog resolveExtensionCatalog() throws RegistryResolutionException { @@ -122,9 +126,10 @@ public static ExtensionCatalog resolveExtensionCatalog() throws RegistryResoluti } public static QuarkusProject getProject(Path projectDir, ExtensionCatalog catalog, ExtensionManager extManager, + JavaVersion javaVersion, MessageWriter log) { return QuarkusProject.of(projectDir, catalog, getCodestartResourceLoaders(catalog), - log, extManager); + log, extManager, javaVersion); } public static ExtensionCatalogResolver getCatalogResolver() throws RegistryResolutionException { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/SourceType.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/SourceType.java similarity index 94% rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/SourceType.java rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/SourceType.java index 7d1e56802cb49..3a25f39f205ab 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/SourceType.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/SourceType.java @@ -1,4 +1,4 @@ -package io.quarkus.devtools.commands; +package io.quarkus.devtools.project; import java.util.Collection; import java.util.Map; diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenProjectBuildFile.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenProjectBuildFile.java index 9758736e8e320..cc0a2169469c6 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenProjectBuildFile.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenProjectBuildFile.java @@ -31,6 +31,7 @@ import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils; import io.quarkus.devtools.messagewriter.MessageWriter; import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.JavaVersion; import io.quarkus.devtools.project.QuarkusProject; import io.quarkus.devtools.project.QuarkusProjectHelper; import io.quarkus.maven.dependency.ArtifactCoords; @@ -122,8 +123,22 @@ public static QuarkusProject getProject(Artifact projectPom, Model projectModel, projectModel, deps, managedDeps, projectProps, projectPom == null ? null : artifactResolver); final List codestartResourceLoaders = codestartLoadersBuilder().catalog(extensionCatalog) .artifactResolver(artifactResolver).build(); + final JavaVersion javaVersion = resolveJavaVersion(projectProps); return QuarkusProject.of(projectDir, extensionCatalog, - codestartResourceLoaders, log, extensionManager); + codestartResourceLoaders, log, extensionManager, javaVersion); + } + + private static JavaVersion resolveJavaVersion(Properties projectProps) { + if (projectProps.containsKey("maven.compiler.release")) { + return new JavaVersion(projectProps.getProperty("maven.compiler.release")); + } + if (projectProps.containsKey("maven.compiler.target")) { + return new JavaVersion(projectProps.getProperty("maven.compiler.target")); + } + if (projectProps.containsKey("maven.compiler.source")) { + return new JavaVersion(projectProps.getProperty("maven.compiler.source")); + } + return JavaVersion.NA; } private static MavenArtifactResolver getMavenResolver(Path projectDir) { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectExtensionsUpdateInfo.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectExtensionsUpdateInfo.java index e4cb0c6445605..a9e488a8bdb88 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectExtensionsUpdateInfo.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectExtensionsUpdateInfo.java @@ -2,8 +2,11 @@ import java.util.List; import java.util.Map; +import java.util.OptionalInt; +import java.util.stream.Stream; import io.quarkus.maven.dependency.ArtifactCoords; +import io.quarkus.platform.catalog.processor.ExtensionProcessor; public class ProjectExtensionsUpdateInfo { final Map> versionedManagedExtensions; @@ -36,6 +39,13 @@ public Map> getNonPlatformExtensions() { return nonPlatformExtensions; } + public OptionalInt getMinJavaVersion() { + return Stream.concat(getVersionedManagedExtensions().values().stream(), getNonPlatformExtensions().values().stream()) + .flatMap(List::stream) + .mapToInt(e -> ExtensionProcessor.getMinimumJavaVersion(e.getRecommendedMetadata())) + .max(); + } + public boolean isEmpty() { return versionedManagedExtensions.isEmpty() && removedExtensions.isEmpty() diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdates.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdates.java index 3520a54cadeda..8321adc4813e4 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdates.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdates.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.OptionalInt; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.devtools.messagewriter.MessageWriter; @@ -52,20 +53,24 @@ public static FetchResult createRecipe(MessageWriter log, Path target, MavenArti public static class ProjectUpdateRequest { - public BuildTool buildTool; - public String currentVersion; - public String targetVersion; - public String kotlinVersion; + public final BuildTool buildTool; + public final String currentVersion; + public final String targetVersion; + public final String kotlinVersion; + public final OptionalInt minJavaVersion; - public ProjectUpdateRequest(String currentVersion, String targetVersion, String kotlinVersion) { - this(BuildTool.MAVEN, currentVersion, targetVersion, kotlinVersion); + public ProjectUpdateRequest(String currentVersion, String targetVersion, String kotlinVersion, + OptionalInt minJavaVersion) { + this(BuildTool.MAVEN, currentVersion, targetVersion, kotlinVersion, minJavaVersion); } - public ProjectUpdateRequest(BuildTool buildTool, String currentVersion, String targetVersion, String kotlinVersion) { + public ProjectUpdateRequest(BuildTool buildTool, String currentVersion, String targetVersion, String kotlinVersion, + OptionalInt minJavaVersion) { this.buildTool = buildTool; this.currentVersion = currentVersion; this.targetVersion = targetVersion; this.kotlinVersion = kotlinVersion; + this.minJavaVersion = minJavaVersion; } } } diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/CreateProjectHelperTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/JavaVersionTest.java similarity index 75% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/CreateProjectHelperTest.java rename to independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/JavaVersionTest.java index bb7a7774ed9c3..285a582e81b2c 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/CreateProjectHelperTest.java +++ b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/JavaVersionTest.java @@ -1,15 +1,15 @@ -package io.quarkus.devtools.commands; +package io.quarkus.devtools.project; -import static io.quarkus.devtools.commands.CreateProjectHelper.DETECT_JAVA_RUNTIME_VERSION; -import static io.quarkus.devtools.commands.CreateProjectHelper.computeJavaVersion; -import static io.quarkus.devtools.commands.CreateProjectHelper.determineBestJavaLtsVersion; -import static io.quarkus.devtools.commands.SourceType.JAVA; -import static io.quarkus.devtools.commands.SourceType.KOTLIN; +import static io.quarkus.devtools.project.JavaVersion.DETECT_JAVA_RUNTIME_VERSION; +import static io.quarkus.devtools.project.JavaVersion.computeJavaVersion; +import static io.quarkus.devtools.project.JavaVersion.determineBestJavaLtsVersion; +import static io.quarkus.devtools.project.SourceType.JAVA; +import static io.quarkus.devtools.project.SourceType.KOTLIN; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; -class CreateProjectHelperTest { +class JavaVersionTest { @Test public void givenJavaVersion17ShouldReturn17() { diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java index a9a98ad034e3c..725aa50bb269c 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java @@ -1,6 +1,6 @@ package io.quarkus.gradle; -import static io.quarkus.devtools.commands.SourceType.JAVA; +import static io.quarkus.devtools.project.SourceType.JAVA; import static org.assertj.core.api.Assertions.assertThat; import java.io.File; @@ -20,7 +20,7 @@ import com.google.common.collect.ImmutableMap; import io.quarkus.devtools.commands.CreateProject; -import io.quarkus.devtools.commands.SourceType; +import io.quarkus.devtools.project.SourceType; import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.QuarkusProjectHelper; import io.quarkus.test.devmode.util.DevModeTestUtils; From 933c0632fa9394ef8e5a85b7292c60e894d2707e Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Mon, 19 Jun 2023 14:47:57 +0200 Subject: [PATCH 09/22] Update java version from recipe (cherry picked from commit 0d28057de8d9c1a8b6ade5dad3ef41eebbcd78bd) --- .../main/java/io/quarkus/cli/CreateApp.java | 2 +- .../main/java/io/quarkus/cli/CreateCli.java | 2 +- .../quarkus/cli/create/BaseCreateCommand.java | 2 +- .../cli/create/TargetLanguageGroup.java | 2 +- .../cli/registry/RegistryClientMixin.java | 3 +- .../io/quarkus/maven/CreateProjectMojo.java | 2 +- .../handlers/UpdateProjectCommandHandler.java | 54 ++++++++++--------- .../project/QuarkusProjectHelper.java | 4 ++ .../project/update/ExtensionMapBuilder.java | 3 +- .../update/ProjectExtensionsUpdateInfo.java | 37 +++++++++---- .../project/update/ProjectUpdateInfos.java | 16 ++++-- .../update/rewrite/QuarkusUpdateRecipeIO.java | 2 +- .../update/rewrite/QuarkusUpdates.java | 18 ++++--- .../update/rewrite/RewriteOperation.java | 10 +++- .../UpdateDependencyVersionOperation.java | 2 +- .../UpdateJavaVersionOperation.java | 36 +++++++++++++ ...dateManagedDependencyVersionOperation.java | 2 +- .../operations/UpdatePropertyOperation.java | 6 +-- .../UpgradeGradlePluginOperation.java | 6 +-- .../gradle/QuarkusPluginFunctionalTest.java | 2 +- 20 files changed, 144 insertions(+), 67 deletions(-) create mode 100644 independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateJavaVersionOperation.java diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java index 7630ef8928d3b..09adc3e85a1f0 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java @@ -12,11 +12,11 @@ import io.quarkus.cli.create.TargetGAVGroup; import io.quarkus.cli.create.TargetLanguageGroup; import io.quarkus.devtools.commands.CreateProject.CreateProjectKey; -import io.quarkus.devtools.project.SourceType; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.handlers.CreateJBangProjectCommandHandler; import io.quarkus.devtools.commands.handlers.CreateProjectCommandHandler; import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.SourceType; import picocli.CommandLine; @CommandLine.Command(name = "app", header = "Create a Quarkus application project.", description = "%n" diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java index d17c38360d252..70fcd130c31fb 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java @@ -12,11 +12,11 @@ import io.quarkus.cli.create.TargetGAVGroup; import io.quarkus.cli.create.TargetLanguageGroup; import io.quarkus.devtools.commands.CreateProject.CreateProjectKey; -import io.quarkus.devtools.project.SourceType; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.handlers.CreateJBangProjectCommandHandler; import io.quarkus.devtools.commands.handlers.CreateProjectCommandHandler; import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.SourceType; import picocli.CommandLine; @CommandLine.Command(name = "cli", header = "Create a Quarkus command-line project.", description = "%n" diff --git a/devtools/cli/src/main/java/io/quarkus/cli/create/BaseCreateCommand.java b/devtools/cli/src/main/java/io/quarkus/cli/create/BaseCreateCommand.java index dc58dcbb4a7a4..439b135dce1cb 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/create/BaseCreateCommand.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/create/BaseCreateCommand.java @@ -17,10 +17,10 @@ import io.quarkus.cli.registry.ToggleRegistryClientMixin; import io.quarkus.devtools.commands.CreateProject.CreateProjectKey; import io.quarkus.devtools.commands.CreateProjectHelper; -import io.quarkus.devtools.project.SourceType; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.devtools.project.SourceType; import io.quarkus.registry.RegistryResolutionException; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; diff --git a/devtools/cli/src/main/java/io/quarkus/cli/create/TargetLanguageGroup.java b/devtools/cli/src/main/java/io/quarkus/cli/create/TargetLanguageGroup.java index 69f0a830d6ccd..92cc237c0c486 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/create/TargetLanguageGroup.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/create/TargetLanguageGroup.java @@ -5,9 +5,9 @@ import java.util.stream.Collectors; import io.quarkus.cli.common.OutputOptionMixin; -import io.quarkus.devtools.project.SourceType; import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.JavaVersion; +import io.quarkus.devtools.project.SourceType; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.ParameterException; diff --git a/devtools/cli/src/main/java/io/quarkus/cli/registry/RegistryClientMixin.java b/devtools/cli/src/main/java/io/quarkus/cli/registry/RegistryClientMixin.java index 635a851c27ec4..7739f49d588f5 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/registry/RegistryClientMixin.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/registry/RegistryClientMixin.java @@ -9,6 +9,7 @@ import io.quarkus.cli.common.TargetQuarkusPlatformGroup; import io.quarkus.devtools.commands.CreateProjectHelper; import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.JavaVersion; import io.quarkus.devtools.project.QuarkusProject; import io.quarkus.devtools.project.QuarkusProjectHelper; import io.quarkus.maven.dependency.ArtifactCoords; @@ -62,7 +63,7 @@ public QuarkusProject createQuarkusProject(Path projectRoot, TargetQuarkusPlatfo + "Use the maven/gradle plugins when working with Quarkus 1.x projects."); } catalog = CreateProjectHelper.completeCatalog(catalog, extensions, QuarkusProjectHelper.artifactResolver()); - return QuarkusProjectHelper.getProject(projectRoot, catalog, buildTool, log); + return QuarkusProjectHelper.getProject(projectRoot, catalog, buildTool, JavaVersion.NA, log); } ExtensionCatalog getExtensionCatalog(TargetQuarkusPlatformGroup targetVersion, OutputOptionMixin log) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java index 15fd70e323906..254dded675ed4 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java @@ -298,7 +298,7 @@ public void execute() throws MojoExecutionException { .artifactResolver(mvn) .build(); QuarkusProject newProject = QuarkusProject.of(projectDirPath, catalog, - codestartsResourceLoader, log, buildToolEnum, new JavaVersion(javaVersion, javaVersion)); + codestartsResourceLoader, log, buildToolEnum, new JavaVersion(javaVersion)); final CreateProject createProject = new CreateProject(newProject) .groupId(projectGroupId) .artifactId(projectArtifactId) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java index 6b4eb609af4d5..c57d632913688 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java @@ -11,6 +11,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.OptionalInt; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.devtools.commands.UpdateProject; @@ -76,13 +78,20 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws if (!noRewrite) { final BuildTool buildTool = quarkusProject.getExtensionManager().getBuildTool(); String kotlinVersion = getMetadata(targetCatalog, "project", "properties", "kotlin-version"); - + final OptionalInt minJavaVersion = extensionsUpdateInfo.getMinJavaVersion(); + final Optional updateJavaVersion; + if (minJavaVersion.isPresent() + && minJavaVersion.getAsInt() > invocation.getQuarkusProject().getJavaVersion().getAsInt()) { + updateJavaVersion = Optional.of(minJavaVersion.getAsInt()); + } else { + updateJavaVersion = Optional.empty(); + } QuarkusUpdates.ProjectUpdateRequest request = new QuarkusUpdates.ProjectUpdateRequest( buildTool, projectQuarkusPlatformBom.getVersion(), targetPlatformVersion, kotlinVersion, - extensionsUpdateInfo.getMinJavaVersion()); + updateJavaVersion); Path recipe = null; try { recipe = Files.createTempFile("quarkus-project-recipe-", ".yaml"); @@ -91,6 +100,7 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws final QuarkusUpdatesRepository.FetchResult fetchResult = QuarkusUpdates.createRecipe(invocation.log(), recipe, QuarkusProjectHelper.artifactResolver(), buildTool, updateRecipesVersion, request); + invocation.log().info("OpenRewrite recipe generated: %s", recipe); final String rewritePluginVersion = invocation.getValue(UpdateProject.REWRITE_PLUGIN_VERSION, fetchResult.getRewritePluginVersion()); final boolean rewriteDryRun = invocation.getValue(UpdateProject.REWRITE_DRY_RUN, false); @@ -108,15 +118,6 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws throw new QuarkusCommandException("Error while generating the project update script", e); } catch (QuarkusUpdateException e) { throw new QuarkusCommandException("Error while running the project update script", e); - } finally { - if (recipe != null) { - try { - Files.deleteIfExists(recipe); - } catch (IOException e) { - // ignore - } - } - } } } @@ -180,13 +181,19 @@ private static void logUpdates(QuarkusProject project, ProjectState currentState log.info(""); } - if (extensionsUpdateInfo.isEmpty()) { - if (!platformUpdateInfo.isPlatformUpdatesAvailable()) { - log.info("The project is up-to-date"); - } + if (extensionsUpdateInfo.isUpToDate() && !platformUpdateInfo.isPlatformUpdatesAvailable()) { + log.info("The project is up-to-date"); return; } + if (extensionsUpdateInfo.getMinJavaVersion().isPresent()) { + final Integer extensionsMinJavaVersion = extensionsUpdateInfo.getMinJavaVersion().getAsInt(); + if (extensionsMinJavaVersion > project.getJavaVersion().getAsInt()) { + log.warn("We detected that some of the updated extensions require an update of the Java version to: %s", + extensionsMinJavaVersion); + } + } + for (PlatformInfo platform : platformUpdateInfo.getPlatformImports().values()) { final String provider = platform.getRecommendedProviderKey(); if (!extensionsUpdateInfo.getVersionedManagedExtensions().containsKey(provider) @@ -203,14 +210,15 @@ private static void logUpdates(QuarkusProject project, ProjectState currentState sb.append(" -> remove version (managed)"); log.info(sb.toString()); } - for (ArtifactCoords e : extensionsUpdateInfo.getAddedExtensions().getOrDefault(provider, Collections.emptyList())) { + for (ExtensionUpdateInfo i : extensionsUpdateInfo.getAddedExtensions().getOrDefault(provider, + Collections.emptyList())) { log.info(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, UpdateProjectCommandHandler.ADD, - e.getKey().toGacString())); + i.getRecommendedDependency().getKey().toGacString())); } - for (ArtifactCoords e : extensionsUpdateInfo.getRemovedExtensions().getOrDefault(provider, + for (ExtensionUpdateInfo i : extensionsUpdateInfo.getRemovedExtensions().getOrDefault(provider, Collections.emptyList())) { log.info(String.format(UpdateProjectCommandHandler.ITEM_FORMAT, UpdateProjectCommandHandler.REMOVE, - e.getKey().toGacString())); + i.getCurrentDep().getKey().toGacString())); } log.info(""); } @@ -237,13 +245,7 @@ private static void logUpdates(QuarkusProject project, ProjectState currentState log.info(""); } } - if (extensionsUpdateInfo.getMinJavaVersion().isPresent()) { - final int extensionsMinJavaVersion = extensionsUpdateInfo.getMinJavaVersion().getAsInt(); - if (extensionsMinJavaVersion > project.getJavaVersion().getAsInt()) { - log.warn("We detected that some of the updated extensions require an update of the Java version to: ", - extensionsMinJavaVersion); - } - } + } @SuppressWarnings({ "rawtypes", "unchecked" }) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java index d559cead7f548..bf50251b319e1 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java @@ -110,6 +110,10 @@ public static QuarkusProject getProject(Path projectDir, ExtensionCatalog catalo return getProject(projectDir, catalog, buildTool, javaVersion, messageWriter()); } + public static QuarkusProject getProject(Path projectDir, ExtensionCatalog catalog, BuildTool buildTool) { + return getProject(projectDir, catalog, buildTool, JavaVersion.NA, messageWriter()); + } + public static QuarkusProject getProject(Path projectDir, ExtensionCatalog catalog, BuildTool buildTool, JavaVersion javaVersion, MessageWriter log) { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ExtensionMapBuilder.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ExtensionMapBuilder.java index e91c09f14670b..d3b98c4af43ac 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ExtensionMapBuilder.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ExtensionMapBuilder.java @@ -87,7 +87,8 @@ public ExtensionUpdateInfoBuilder setRecommendedDep(TopExtensionDependency recom } public ExtensionUpdateInfo build() { - return new ExtensionUpdateInfo(currentDep, currentDep.getCatalogMetadata(), resolveRecommendedDep()); + final TopExtensionDependency effectiveRecommendedDep = resolveRecommendedDep(); + return new ExtensionUpdateInfo(currentDep, effectiveRecommendedDep.getCatalogMetadata(), effectiveRecommendedDep); } public TopExtensionDependency resolveRecommendedDep() { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectExtensionsUpdateInfo.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectExtensionsUpdateInfo.java index a9e488a8bdb88..b25631f6642d9 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectExtensionsUpdateInfo.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectExtensionsUpdateInfo.java @@ -1,37 +1,47 @@ package io.quarkus.devtools.project.update; +import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.OptionalInt; import java.util.stream.Stream; -import io.quarkus.maven.dependency.ArtifactCoords; +import io.quarkus.devtools.project.JavaVersion; import io.quarkus.platform.catalog.processor.ExtensionProcessor; public class ProjectExtensionsUpdateInfo { + private final Map> managedExtensions; final Map> versionedManagedExtensions; - final Map> removedExtensions; - final Map> addedExtensions; + final Map> removedExtensions; + final Map> addedExtensions; final Map> nonPlatformExtensions; - public ProjectExtensionsUpdateInfo(Map> versionedManagedExtensions, - Map> removedExtensions, Map> addedExtensions, + public ProjectExtensionsUpdateInfo(Map> managedExtensions, + Map> versionedManagedExtensions, + Map> removedExtensions, + Map> addedExtensions, Map> nonPlatformExtensionUpdate) { + this.managedExtensions = managedExtensions; this.versionedManagedExtensions = versionedManagedExtensions; this.removedExtensions = removedExtensions; this.addedExtensions = addedExtensions; this.nonPlatformExtensions = nonPlatformExtensionUpdate; } + public Map> getManagedExtensions() { + return managedExtensions; + } + public Map> getVersionedManagedExtensions() { return versionedManagedExtensions; } - public Map> getRemovedExtensions() { + public Map> getRemovedExtensions() { return removedExtensions; } - public Map> getAddedExtensions() { + public Map> getAddedExtensions() { return addedExtensions; } @@ -40,13 +50,18 @@ public Map> getNonPlatformExtensions() { } public OptionalInt getMinJavaVersion() { - return Stream.concat(getVersionedManagedExtensions().values().stream(), getNonPlatformExtensions().values().stream()) - .flatMap(List::stream) - .mapToInt(e -> ExtensionProcessor.getMinimumJavaVersion(e.getRecommendedMetadata())) + return Stream.of(getManagedExtensions().values(), + getVersionedManagedExtensions().values(), + getNonPlatformExtensions().values(), + getAddedExtensions().values()) + .flatMap(Collection::stream) + .flatMap(Collection::stream) + .mapToInt(e -> Optional.ofNullable(ExtensionProcessor.getMinimumJavaVersion(e.getRecommendedMetadata())) + .orElse(JavaVersion.DEFAULT_JAVA_VERSION)) .max(); } - public boolean isEmpty() { + public boolean isUpToDate() { return versionedManagedExtensions.isEmpty() && removedExtensions.isEmpty() && addedExtensions.isEmpty() diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectUpdateInfos.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectUpdateInfos.java index 34443d1a17209..bb3d978504fdf 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectUpdateInfos.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectUpdateInfos.java @@ -43,9 +43,10 @@ public static ProjectExtensionsUpdateInfo resolveExtensionsUpdateInfo(ProjectSta info.setRecommendedDep(dep); } } + final Map> managedExtensions = new LinkedHashMap<>(0); final Map> versionedManagedExtensions = new LinkedHashMap<>(0); - final Map> removedExtensions = new LinkedHashMap<>(0); - final Map> addedExtensions = new LinkedHashMap<>(0); + final Map> removedExtensions = new LinkedHashMap<>(0); + final Map> addedExtensions = new LinkedHashMap<>(0); final Map> nonPlatformExtensionUpdates = new LinkedHashMap<>(); for (ExtensionUpdateInfoBuilder infoBuilder : extensionInfo.values()) { final ExtensionUpdateInfo info = infoBuilder.build(); @@ -55,14 +56,14 @@ public static ProjectExtensionsUpdateInfo resolveExtensionsUpdateInfo(ProjectSta if (!info.getCurrentDep().getKey().equals(info.getRecommendedDependency().getKey())) { if (info.getCurrentDep().isPlatformExtension()) { removedExtensions.computeIfAbsent(info.getCurrentDep().getProviderKey(), k -> new ArrayList<>()) - .add(info.getCurrentDep().getArtifact()); + .add(new ExtensionUpdateInfo(info.getCurrentDep(), null, null)); } else { nonPlatformExtensionUpdates.computeIfAbsent(info.getCurrentDep().getProviderKey(), k -> new ArrayList<>()) .add(info); } if (info.getRecommendedDependency().isPlatformExtension()) { addedExtensions.computeIfAbsent(info.getRecommendedDependency().getProviderKey(), k -> new ArrayList<>()) - .add(info.getRecommendedDependency().getArtifact()); + .add(new ExtensionUpdateInfo(null, info.getRecommendedMetadata(), info.getRecommendedDependency())); } else { nonPlatformExtensionUpdates .computeIfAbsent(info.getRecommendedDependency().getProviderKey(), k -> new ArrayList<>()) @@ -73,13 +74,18 @@ public static ProjectExtensionsUpdateInfo resolveExtensionsUpdateInfo(ProjectSta versionedManagedExtensions .computeIfAbsent(info.getRecommendedDependency().getProviderKey(), k -> new ArrayList<>()) .add(info); + } else { + managedExtensions + .computeIfAbsent(info.getRecommendedDependency().getProviderKey(), k -> new ArrayList<>()) + .add(info); } } else if (!info.getCurrentDep().getVersion().equals(info.getRecommendedDependency().getVersion())) { nonPlatformExtensionUpdates .computeIfAbsent(info.getRecommendedDependency().getProviderKey(), k -> new ArrayList<>()).add(info); } } - return new ProjectExtensionsUpdateInfo(versionedManagedExtensions, removedExtensions, addedExtensions, + return new ProjectExtensionsUpdateInfo(managedExtensions, versionedManagedExtensions, removedExtensions, + addedExtensions, nonPlatformExtensionUpdates); } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateRecipeIO.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateRecipeIO.java index c6f6485ddb0d0..0588ab47baba8 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateRecipeIO.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateRecipeIO.java @@ -47,7 +47,7 @@ static String toYaml(QuarkusUpdateRecipe recipe) { q.putAll(QuarkusUpdateRecipe.QUARKUS_RECIPE); List recipeList = new ArrayList<>(); for (RewriteOperation o : recipe.getOperations()) { - recipeList.add(o.toMap(recipe.getBuildTool())); + recipeList.addAll(o.multi(recipe.getBuildTool())); } recipeList.addAll(recipe.getOtherRecipeNames()); q.put(QuarkusUpdateRecipe.RECIPE_LIST_KEY, recipeList); diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdates.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdates.java index 8321adc4813e4..b5c45ab54e708 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdates.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdates.java @@ -2,12 +2,13 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.OptionalInt; +import java.util.Optional; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.devtools.messagewriter.MessageWriter; import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.update.rewrite.QuarkusUpdatesRepository.FetchResult; +import io.quarkus.devtools.project.update.rewrite.operations.UpdateJavaVersionOperation; import io.quarkus.devtools.project.update.rewrite.operations.UpdatePropertyOperation; import io.quarkus.devtools.project.update.rewrite.operations.UpgradeGradlePluginOperation; @@ -25,7 +26,10 @@ public static FetchResult createRecipe(MessageWriter log, Path target, MavenArti request.targetVersion); QuarkusUpdateRecipe recipe = new QuarkusUpdateRecipe() .buildTool(request.buildTool); - + if (request.updateJavaVersion.isPresent()) { + final String javaVersion = request.updateJavaVersion.get().toString(); + recipe.addOperation(new UpdateJavaVersionOperation(javaVersion)); + } switch (request.buildTool) { case MAVEN: recipe.addOperation(new UpdatePropertyOperation("quarkus.platform.version", request.targetVersion)) @@ -57,20 +61,20 @@ public static class ProjectUpdateRequest { public final String currentVersion; public final String targetVersion; public final String kotlinVersion; - public final OptionalInt minJavaVersion; + public final Optional updateJavaVersion; public ProjectUpdateRequest(String currentVersion, String targetVersion, String kotlinVersion, - OptionalInt minJavaVersion) { - this(BuildTool.MAVEN, currentVersion, targetVersion, kotlinVersion, minJavaVersion); + Optional updateJavaVersion) { + this(BuildTool.MAVEN, currentVersion, targetVersion, kotlinVersion, updateJavaVersion); } public ProjectUpdateRequest(BuildTool buildTool, String currentVersion, String targetVersion, String kotlinVersion, - OptionalInt minJavaVersion) { + Optional updateJavaVersion) { this.buildTool = buildTool; this.currentVersion = currentVersion; this.targetVersion = targetVersion; this.kotlinVersion = kotlinVersion; - this.minJavaVersion = minJavaVersion; + this.updateJavaVersion = updateJavaVersion; } } } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/RewriteOperation.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/RewriteOperation.java index 7e69d0e413720..e0557a7aa5b8e 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/RewriteOperation.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/RewriteOperation.java @@ -1,5 +1,6 @@ package io.quarkus.devtools.project.update.rewrite; +import java.util.List; import java.util.Map; import io.quarkus.devtools.project.BuildTool; @@ -17,5 +18,12 @@ public interface RewriteOperation { * @param buildTool * @return */ - Map toMap(BuildTool buildTool); + default Map single(BuildTool buildTool) { + return Map.of(); + } + + default List> multi(BuildTool buildTool) { + return List.of(single(buildTool)); + } + } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateDependencyVersionOperation.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateDependencyVersionOperation.java index ea47c75b415c6..18c6f1c58741b 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateDependencyVersionOperation.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateDependencyVersionOperation.java @@ -18,7 +18,7 @@ public UpdateDependencyVersionOperation(String groupId, String artifactId, Strin } @Override - public Map toMap(BuildTool buildTool) { + public Map single(BuildTool buildTool) { switch (buildTool) { case MAVEN: return Map.of("org.openrewrite.maven.UpgradeDependencyVersion", diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateJavaVersionOperation.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateJavaVersionOperation.java new file mode 100644 index 0000000000000..ffdd9e194d3a2 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateJavaVersionOperation.java @@ -0,0 +1,36 @@ +package io.quarkus.devtools.project.update.rewrite.operations; + +import java.util.List; +import java.util.Map; + +import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.update.rewrite.RewriteOperation; + +public class UpdateJavaVersionOperation implements RewriteOperation { + + private final String newVersion; + + public UpdateJavaVersionOperation(String newVersion) { + this.newVersion = newVersion; + } + + @Override + public List> multi(BuildTool buildTool) { + switch (buildTool) { + case MAVEN: + return List.of( + Map.of("org.openrewrite.maven.ChangePropertyValue", + Map.of("key", "maven.compiler.source", "newValue", newVersion)), + Map.of("org.openrewrite.maven.ChangePropertyValue", + Map.of("key", "maven.compiler.target", "newValue", newVersion)), + Map.of("org.openrewrite.maven.ChangePropertyValue", + Map.of("key", "maven.compiler.release", "newValue", newVersion))); + case GRADLE: + return List.of(Map.of( + "org.openrewrite.gradle.UpdateJavaCompatibility", + Map.of("version", newVersion))); + default: + return List.of(); + } + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateManagedDependencyVersionOperation.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateManagedDependencyVersionOperation.java index 172e077f0a1a2..1d1dc8677ae9d 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateManagedDependencyVersionOperation.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdateManagedDependencyVersionOperation.java @@ -18,7 +18,7 @@ public UpdateManagedDependencyVersionOperation(String groupId, String artifactId } @Override - public Map toMap(BuildTool buildTool) { + public Map single(BuildTool buildTool) { switch (buildTool) { case MAVEN: return Map.of("org.openrewrite.maven.ChangeManagedDependencyGroupIdAndArtifactId", diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdatePropertyOperation.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdatePropertyOperation.java index 0929c5a8780ae..4a8cc0f65c2d3 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdatePropertyOperation.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpdatePropertyOperation.java @@ -7,8 +7,8 @@ public class UpdatePropertyOperation implements RewriteOperation { - public String key; - public String newValue; + private final String key; + private final String newValue; public UpdatePropertyOperation(String key, String newValue) { this.key = key; @@ -16,7 +16,7 @@ public UpdatePropertyOperation(String key, String newValue) { } @Override - public Map toMap(BuildTool buildTool) { + public Map single(BuildTool buildTool) { switch (buildTool) { case MAVEN: return Map.of("org.openrewrite.maven.ChangePropertyValue", diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpgradeGradlePluginOperation.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpgradeGradlePluginOperation.java index 2fdcbb2a5d71f..a621df7d7a07e 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpgradeGradlePluginOperation.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/operations/UpgradeGradlePluginOperation.java @@ -7,8 +7,8 @@ public class UpgradeGradlePluginOperation implements RewriteOperation { - public String pluginIdPattern; - public String newVersion; + private final String pluginIdPattern; + private final String newVersion; public UpgradeGradlePluginOperation(String pluginIdPattern, String newVersion) { this.pluginIdPattern = pluginIdPattern; @@ -16,7 +16,7 @@ public UpgradeGradlePluginOperation(String pluginIdPattern, String newVersion) { } @Override - public Map toMap(BuildTool buildTool) { + public Map single(BuildTool buildTool) { switch (buildTool) { case GRADLE: return Map.of( diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java index 725aa50bb269c..0530f130015ca 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java @@ -20,9 +20,9 @@ import com.google.common.collect.ImmutableMap; import io.quarkus.devtools.commands.CreateProject; -import io.quarkus.devtools.project.SourceType; import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.QuarkusProjectHelper; +import io.quarkus.devtools.project.SourceType; import io.quarkus.test.devmode.util.DevModeTestUtils; public class QuarkusPluginFunctionalTest extends QuarkusGradleDevToolsTestBase { From d2f38375076b80b38fc82b1570ba5beba0a4041c Mon Sep 17 00:00:00 2001 From: Jose Date: Fri, 4 Aug 2023 08:17:22 +0200 Subject: [PATCH 10/22] REST Data with Panache should not produce links when hal is disabled This is related to https://github.com/quarkusio/quarkus/issues/35167. Even though HAL is disabled by default, it's still used to generate unexpected links and to set the location. (cherry picked from commit 362f03a846af7cba222602c9814bbe0d2e1334f3) --- .../PanacheEntityResourceHalDisabledTest.java | 45 +++++++++++++++++++ .../panache/deployment/entity/Project.java | 13 ++++++ .../deployment/entity/ProjectResource.java | 11 +++++ .../PanacheEntityResourceHalDisabledTest.java | 45 +++++++++++++++++++ .../panache/deployment/entity/Project.java | 13 ++++++ .../deployment/entity/ProjectResource.java | 11 +++++ .../methods/AddMethodImplementor.java | 6 +-- .../methods/CountMethodImplementor.java | 2 +- .../methods/DeleteMethodImplementor.java | 2 +- .../methods/GetMethodImplementor.java | 2 +- .../methods/ListMethodImplementor.java | 4 +- .../methods/StandardMethodImplementor.java | 29 ++++++------ .../methods/UpdateMethodImplementor.java | 20 +++++---- .../deployment/utils/ResponseImplementor.java | 28 +++++++----- 14 files changed, 191 insertions(+), 40 deletions(-) create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/entity/PanacheEntityResourceHalDisabledTest.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/entity/Project.java create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/entity/ProjectResource.java create mode 100644 extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/entity/PanacheEntityResourceHalDisabledTest.java create mode 100644 extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/entity/Project.java create mode 100644 extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/entity/ProjectResource.java diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/entity/PanacheEntityResourceHalDisabledTest.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/entity/PanacheEntityResourceHalDisabledTest.java new file mode 100644 index 0000000000000..5e0d77a15b98f --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/entity/PanacheEntityResourceHalDisabledTest.java @@ -0,0 +1,45 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.entity; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.response.Response; + +class PanacheEntityResourceHalDisabledTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(Project.class, ProjectResource.class) + .addAsResource("application.properties")); + + @Test + void shouldHalNotBeSupported() { + given().accept("application/hal+json") + .when().get("/group/projects/1") + .then().statusCode(406); + } + + @Test + void shouldNotContainLocationAndLinks() { + Response response = given().accept("application/json") + .and().contentType("application/json") + .and().body("{\"name\": \"projectname\"}") + .when().post("/group/projects") + .thenReturn(); + assertThat(response.statusCode()).isEqualTo(201); + assertThat(response.header("Location")).isBlank(); + assertThat(response.getHeaders().getList("Link")).isEmpty(); + + response = given().accept("application/json") + .when().get("/group/projects/projectname") + .thenReturn(); + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.header("Location")).isBlank(); + assertThat(response.getHeaders().getList("Link")).isEmpty(); + } +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/entity/Project.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/entity/Project.java new file mode 100644 index 0000000000000..4847b31c9d054 --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/entity/Project.java @@ -0,0 +1,13 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; + +@Entity +public class Project extends PanacheEntityBase { + + @Id + public String name; +} diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/entity/ProjectResource.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/entity/ProjectResource.java new file mode 100644 index 0000000000000..1a939ff3ce66f --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/entity/ProjectResource.java @@ -0,0 +1,11 @@ +package io.quarkus.hibernate.orm.rest.data.panache.deployment.entity; + +import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource; +import io.quarkus.rest.data.panache.ResourceProperties; + +/** + * Having a path param in the path reproduces the issue of having HAL enabled spites it should be disabled by default. + */ +@ResourceProperties(path = "/{group}/projects") +public interface ProjectResource extends PanacheEntityResource { +} diff --git a/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/entity/PanacheEntityResourceHalDisabledTest.java b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/entity/PanacheEntityResourceHalDisabledTest.java new file mode 100644 index 0000000000000..aad658564a479 --- /dev/null +++ b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/entity/PanacheEntityResourceHalDisabledTest.java @@ -0,0 +1,45 @@ +package io.quarkus.hibernate.reactive.rest.data.panache.deployment.entity; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.response.Response; + +class PanacheEntityResourceHalDisabledTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(Project.class, ProjectResource.class) + .addAsResource("application.properties")); + + @Test + void shouldHalNotBeSupported() { + given().accept("application/hal+json") + .when().get("/group/projects/1") + .then().statusCode(406); + } + + @Test + void shouldNotContainLocationAndLinks() { + Response response = given().accept("application/json") + .and().contentType("application/json") + .and().body("{\"name\": \"projectname\"}") + .when().post("/group/projects") + .thenReturn(); + assertThat(response.statusCode()).isEqualTo(201); + assertThat(response.header("Location")).isBlank(); + assertThat(response.getHeaders().getList("Link")).isEmpty(); + + response = given().accept("application/json") + .when().get("/group/projects/projectname") + .thenReturn(); + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.header("Location")).isBlank(); + assertThat(response.getHeaders().getList("Link")).isEmpty(); + } +} diff --git a/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/entity/Project.java b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/entity/Project.java new file mode 100644 index 0000000000000..c5398ea9d88c9 --- /dev/null +++ b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/entity/Project.java @@ -0,0 +1,13 @@ +package io.quarkus.hibernate.reactive.rest.data.panache.deployment.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import io.quarkus.hibernate.reactive.panache.PanacheEntityBase; + +@Entity +public class Project extends PanacheEntityBase { + + @Id + public String name; +} diff --git a/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/entity/ProjectResource.java b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/entity/ProjectResource.java new file mode 100644 index 0000000000000..c22a85d8899a3 --- /dev/null +++ b/extensions/panache/hibernate-reactive-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/rest/data/panache/deployment/entity/ProjectResource.java @@ -0,0 +1,11 @@ +package io.quarkus.hibernate.reactive.rest.data.panache.deployment.entity; + +import io.quarkus.hibernate.reactive.rest.data.panache.PanacheEntityResource; +import io.quarkus.rest.data.panache.ResourceProperties; + +/** + * Having a path param in the path reproduces the issue of having HAL enabled spites it should be disabled by default. + */ +@ResourceProperties(path = "/{group}/projects") +public interface ProjectResource extends PanacheEntityResource { +} diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/AddMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/AddMethodImplementor.java index f3f719f766948..eb862c9601d9f 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/AddMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/AddMethodImplementor.java @@ -114,7 +114,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res addContextAnnotation(methodCreator.getParameterAnnotations(1)); addConsumesAnnotation(methodCreator, APPLICATION_JSON); addProducesJsonAnnotation(methodCreator, resourceProperties); - addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL); + addLinksAnnotation(methodCreator, resourceProperties, resourceMetadata.getEntityType(), REL); addOpenApiResponseAnnotation(methodCreator, Response.Status.CREATED, resourceMetadata.getEntityType()); addSecurityAnnotations(methodCreator, resourceProperties); // Add parameter annotations @@ -130,7 +130,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res ResultHandle entity = tryBlock.invokeVirtualMethod( ofMethod(resourceMetadata.getResourceClass(), RESOURCE_METHOD_NAME, Object.class, Object.class), resource, entityToSave); - tryBlock.returnValue(responseImplementor.created(tryBlock, entity)); + tryBlock.returnValue(responseImplementor.created(tryBlock, entity, resourceProperties)); tryBlock.close(); } else { ResultHandle uniEntity = methodCreator.invokeVirtualMethod( @@ -138,7 +138,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res resource, entityToSave); methodCreator.returnValue(UniImplementor.map(methodCreator, uniEntity, EXCEPTION_MESSAGE, - (body, item) -> body.returnValue(responseImplementor.created(body, item)))); + (body, item) -> body.returnValue(responseImplementor.created(body, item, resourceProperties)))); } methodCreator.close(); diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/CountMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/CountMethodImplementor.java index 334875ce9f402..a77e466b742c8 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/CountMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/CountMethodImplementor.java @@ -87,7 +87,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res if (!isResteasyClassic()) { // We only add the Links annotation in Resteasy Reactive because Resteasy Classic ignores the REL parameter: // it always uses "list" for GET methods, so it interferes with the list implementation. - addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL); + addLinksAnnotation(methodCreator, resourceProperties, resourceMetadata.getEntityType(), REL); } ResultHandle resource = methodCreator.readInstanceField(resourceField, methodCreator.getThis()); diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/DeleteMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/DeleteMethodImplementor.java index 6cb73e874a5b9..0734705ec1869 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/DeleteMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/DeleteMethodImplementor.java @@ -91,7 +91,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res addPathAnnotation(methodCreator, appendToPath(resourceProperties.getPath(RESOURCE_METHOD_NAME), "{id}")); addDeleteAnnotation(methodCreator); addPathParamAnnotation(methodCreator.getParameterAnnotations(0), "id"); - addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL); + addLinksAnnotation(methodCreator, resourceProperties, resourceMetadata.getEntityType(), REL); addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME)); addOpenApiResponseAnnotation(methodCreator, Response.Status.NO_CONTENT); addSecurityAnnotations(methodCreator, resourceProperties); diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/GetMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/GetMethodImplementor.java index 5f59df6fe30a0..098fa9d3049d6 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/GetMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/GetMethodImplementor.java @@ -98,7 +98,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res addSecurityAnnotations(methodCreator, resourceProperties); addPathParamAnnotation(methodCreator.getParameterAnnotations(0), "id"); - addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL); + addLinksAnnotation(methodCreator, resourceProperties, resourceMetadata.getEntityType(), REL); ResultHandle resource = methodCreator.readInstanceField(resourceField, methodCreator.getThis()); ResultHandle id = methodCreator.getMethodParam(0); diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/ListMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/ListMethodImplementor.java index 2c46c6172868f..1b8ec8077590f 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/ListMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/ListMethodImplementor.java @@ -193,7 +193,7 @@ private void implementPaged(ClassCreator classCreator, ResourceMetadata resource addGetAnnotation(methodCreator); addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME)); addProducesJsonAnnotation(methodCreator, resourceProperties); - addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL); + addLinksAnnotation(methodCreator, resourceProperties, resourceMetadata.getEntityType(), REL); addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME)); addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, resourceMetadata.getEntityType(), true); addSecurityAnnotations(methodCreator, resourceProperties); @@ -280,7 +280,7 @@ private void implementNotPaged(ClassCreator classCreator, ResourceMetadata resou addGetAnnotation(methodCreator); addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME)); addProducesJsonAnnotation(methodCreator, resourceProperties); - addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL); + addLinksAnnotation(methodCreator, resourceProperties, resourceMetadata.getEntityType(), REL); addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME)); addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, resourceMetadata.getEntityType(), true); addSecurityAnnotations(methodCreator, resourceProperties); diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/StandardMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/StandardMethodImplementor.java index d12d3a9eb279e..449cfc39e4270 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/StandardMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/StandardMethodImplementor.java @@ -100,20 +100,23 @@ protected void addDeleteAnnotation(AnnotatedElement element) { element.addAnnotation(DELETE.class); } - protected void addLinksAnnotation(AnnotatedElement element, String entityClassName, String rel) { - if (isResteasyClassic()) { - AnnotationCreator linkResource = element.addAnnotation("org.jboss.resteasy.links.LinkResource"); - linkResource.addValue("entityClassName", entityClassName); - linkResource.addValue("rel", rel); - } else { - AnnotationCreator linkResource = element.addAnnotation("io.quarkus.resteasy.reactive.links.RestLink"); - Class entityClass; - try { - entityClass = Thread.currentThread().getContextClassLoader().loadClass(entityClassName); - linkResource.addValue("entityType", entityClass); + protected void addLinksAnnotation(AnnotatedElement element, ResourceProperties resourceProperties, String entityClassName, + String rel) { + if (resourceProperties.isHal()) { + if (isResteasyClassic()) { + AnnotationCreator linkResource = element.addAnnotation("org.jboss.resteasy.links.LinkResource"); + linkResource.addValue("entityClassName", entityClassName); linkResource.addValue("rel", rel); - } catch (ClassNotFoundException e) { - LOGGER.error("Unable to create links for entity: '" + entityClassName + "'", e); + } else { + AnnotationCreator linkResource = element.addAnnotation("io.quarkus.resteasy.reactive.links.RestLink"); + Class entityClass; + try { + entityClass = Thread.currentThread().getContextClassLoader().loadClass(entityClassName); + linkResource.addValue("entityType", entityClass); + linkResource.addValue("rel", rel); + } catch (ClassNotFoundException e) { + LOGGER.error("Unable to create links for entity: '" + entityClassName + "'", e); + } } } } diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UpdateMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UpdateMethodImplementor.java index baa8f7fa0a87b..2f42be5938390 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UpdateMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UpdateMethodImplementor.java @@ -148,7 +148,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res addContextAnnotation(methodCreator.getParameterAnnotations(2)); addConsumesAnnotation(methodCreator, APPLICATION_JSON); addProducesJsonAnnotation(methodCreator, resourceProperties); - addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL); + addLinksAnnotation(methodCreator, resourceProperties, resourceMetadata.getEntityType(), REL); addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_UPDATE_METHOD_NAME)); addOpenApiResponseAnnotation(methodCreator, Response.Status.CREATED, resourceMetadata.getEntityType()); addSecurityAnnotations(methodCreator, resourceProperties); @@ -162,9 +162,9 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res ResultHandle entityToSave = methodCreator.getMethodParam(1); if (isNotReactivePanache()) { - implementClassicVersion(methodCreator, resourceMetadata, resource, id, entityToSave); + implementClassicVersion(methodCreator, resourceMetadata, resourceProperties, resource, id, entityToSave); } else { - implementReactiveVersion(methodCreator, resourceMetadata, resource, id, entityToSave); + implementReactiveVersion(methodCreator, resourceMetadata, resourceProperties, resource, id, entityToSave); } methodCreator.close(); @@ -175,8 +175,8 @@ protected String getResourceMethodName() { return RESOURCE_UPDATE_METHOD_NAME; } - private void implementReactiveVersion(MethodCreator methodCreator, ResourceMetadata resourceMetadata, ResultHandle resource, - ResultHandle id, ResultHandle entityToSave) { + private void implementReactiveVersion(MethodCreator methodCreator, ResourceMetadata resourceMetadata, + ResourceProperties resourceProperties, ResultHandle resource, ResultHandle id, ResultHandle entityToSave) { ResultHandle uniResponse = methodCreator.invokeVirtualMethod( ofMethod(resourceMetadata.getResourceClass(), RESOURCE_GET_METHOD_NAME, Uni.class, Object.class), resource, id); @@ -193,15 +193,16 @@ private void implementReactiveVersion(MethodCreator methodCreator, ResourceMetad (updateBody, itemUpdated) -> { BranchResult ifEntityIsNew = updateBody.ifNull(itemWasFound); ifEntityIsNew.trueBranch() - .returnValue(responseImplementor.created(ifEntityIsNew.trueBranch(), itemUpdated)); + .returnValue(responseImplementor.created(ifEntityIsNew.trueBranch(), itemUpdated, + resourceProperties)); ifEntityIsNew.falseBranch() .returnValue(responseImplementor.noContent(ifEntityIsNew.falseBranch())); })); })); } - private void implementClassicVersion(MethodCreator methodCreator, ResourceMetadata resourceMetadata, ResultHandle resource, - ResultHandle id, ResultHandle entityToSave) { + private void implementClassicVersion(MethodCreator methodCreator, ResourceMetadata resourceMetadata, + ResourceProperties resourceProperties, ResultHandle resource, ResultHandle id, ResultHandle entityToSave) { // Invoke resource methods inside a supplier function which will be given to an update executor. // For ORM, this update executor will have the @Transactional annotation to make // sure that all database operations are executed in a single transaction. @@ -214,7 +215,8 @@ private void implementClassicVersion(MethodCreator methodCreator, ResourceMetada updateExecutor, updateFunction); BranchResult createdNewEntity = tryBlock.ifNotNull(newEntity); - createdNewEntity.trueBranch().returnValue(responseImplementor.created(createdNewEntity.trueBranch(), newEntity)); + createdNewEntity.trueBranch() + .returnValue(responseImplementor.created(createdNewEntity.trueBranch(), newEntity, resourceProperties)); createdNewEntity.falseBranch().returnValue(responseImplementor.noContent(createdNewEntity.falseBranch())); } diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/utils/ResponseImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/utils/ResponseImplementor.java index e2db59b6b95fd..834ae183d89b5 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/utils/ResponseImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/utils/ResponseImplementor.java @@ -19,6 +19,7 @@ import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; import io.quarkus.hal.HalService; +import io.quarkus.rest.data.panache.deployment.properties.ResourceProperties; import io.quarkus.resteasy.links.runtime.hal.ResteasyHalService; import io.quarkus.resteasy.reactive.links.runtime.hal.ResteasyReactiveHalService; @@ -44,17 +45,12 @@ public ResultHandle ok(BytecodeCreator creator, ResultHandle entity, ResultHandl return creator.invokeVirtualMethod(ofMethod(ResponseBuilder.class, "build", Response.class), builder); } - public ResultHandle created(BytecodeCreator creator, ResultHandle entity) { - return created(creator, entity, getEntityUrl(creator, entity)); - } + public ResultHandle created(BytecodeCreator creator, ResultHandle entity, ResourceProperties resourceProperties) { + if (resourceProperties.isHal()) { + return doCreated(creator, entity, getEntityUrl(creator, entity)); + } - public ResultHandle created(BytecodeCreator creator, ResultHandle entity, ResultHandle location) { - ResultHandle builder = getResponseBuilder(creator, Response.Status.CREATED.getStatusCode()); - creator.invokeVirtualMethod( - ofMethod(ResponseBuilder.class, "entity", ResponseBuilder.class, Object.class), builder, entity); - creator.invokeVirtualMethod( - ofMethod(ResponseBuilder.class, "location", ResponseBuilder.class, URI.class), builder, location); - return creator.invokeVirtualMethod(ofMethod(ResponseBuilder.class, "build", Response.class), builder); + return doCreated(creator, entity, null); } public ResultHandle getEntityUrl(BytecodeCreator creator, ResultHandle entity) { @@ -89,6 +85,18 @@ public ResultHandle notFoundException(BytecodeCreator creator) { creator.load(Response.Status.NOT_FOUND.getStatusCode())); } + private ResultHandle doCreated(BytecodeCreator creator, ResultHandle entity, ResultHandle location) { + ResultHandle builder = getResponseBuilder(creator, Response.Status.CREATED.getStatusCode()); + creator.invokeVirtualMethod( + ofMethod(ResponseBuilder.class, "entity", ResponseBuilder.class, Object.class), builder, entity); + if (location != null) { + creator.invokeVirtualMethod( + ofMethod(ResponseBuilder.class, "location", ResponseBuilder.class, URI.class), builder, location); + } + + return creator.invokeVirtualMethod(ofMethod(ResponseBuilder.class, "build", Response.class), builder); + } + private ResultHandle status(BytecodeCreator creator, int status) { ResultHandle builder = getResponseBuilder(creator, status); return creator.invokeVirtualMethod(ofMethod(ResponseBuilder.class, "build", Response.class), builder); From 9b83a7cb655dc309f5283d798531a0b484ede52d Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 3 Aug 2023 15:46:12 +0200 Subject: [PATCH 11/22] SmallRyeGraphQLOverWebSocketHandler: use order value > Integer.MIN_VALUE - so that it's possible to register a route/filter that is executed before this handler - fixes #34908 (cherry picked from commit 2be7e958493f2a947be72120d03ccfa9b3e55e52) --- .../smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java index 3febad882c0f5..f9743035cf9c5 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java @@ -144,6 +144,8 @@ public class SmallRyeGraphQLProcessor { private static final List SUPPORTED_WEBSOCKET_SUBPROTOCOLS = List.of(SUBPROTOCOL_GRAPHQL_WS, SUBPROTOCOL_GRAPHQL_TRANSPORT_WS); + private static final int GRAPHQL_WEBSOCKET_HANDLER_ORDER = -10000; + @BuildStep void feature(BuildProducer featureProducer) { featureProducer.produce(new FeatureBuildItem(Feature.SMALLRYE_GRAPHQL)); @@ -368,7 +370,7 @@ void buildExecutionEndpoint( runBlocking); HttpRootPathBuildItem.Builder subscriptionsBuilder = httpRootPathBuildItem.routeBuilder() - .orderedRoute(graphQLConfig.rootPath, Integer.MIN_VALUE) + .orderedRoute(graphQLConfig.rootPath, GRAPHQL_WEBSOCKET_HANDLER_ORDER) .handler(graphqlOverWebsocketHandler); routeProducer.produce(subscriptionsBuilder.build()); From 8dde8062debb876b9e63607055ba8636ca53c2e4 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Fri, 4 Aug 2023 17:04:10 +0200 Subject: [PATCH 12/22] Document Maven config options that may be relevant when running tests (cherry picked from commit 1f051ae1bcfbd3b13b75270bc223831aec1d9f0b) --- docs/src/main/asciidoc/maven-tooling.adoc | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/docs/src/main/asciidoc/maven-tooling.adoc b/docs/src/main/asciidoc/maven-tooling.adoc index cc758093937ed..739945d18e025 100644 --- a/docs/src/main/asciidoc/maven-tooling.adoc +++ b/docs/src/main/asciidoc/maven-tooling.adoc @@ -637,6 +637,58 @@ unless it is the main application module already configured with the quarkus-mav More information on this topic can be found on the xref:cdi-reference.adoc#bean_discovery[Bean Discovery] section of the CDI guide. +[[maven-test-configuration]] +=== Maven test plugin configuration + +`maven-surefire-plugin` and `maven-failsafe-plugin` configurations showed above will work in most cases. However, there could be cases when extra configuration will be required. + +The reason is that, Quarkus may need to re-resolve application dependencies during the test phase to set up the test classpath for the tests. The original Maven resolver used in previous build phases +will not be available in the test process and, as a conseqence, Quarkus will need to initialize a new one. To make sure the new resolver is initialized correctly, the relevant configuration options +will need to be passed to the test process. + +==== Maven user settings + +A path to the Maven user settings file may need to be passed to test processes, for example, in case the Maven build process was not launched using the default `mvn` scripts included in the Maven distribution. +It could be done in the following way: + +[source,xml,subs=attributes+] +---- + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire-plugin.version} + + + + ${session.request.userSettingsFile.absolutePath} + + + +---- + +==== Remote repository access through authenticated HTTPS + +In case a remote Maven repository requires link:https://maven.apache.org/guides/mini/guide-repository-ssl.html[authenticated HTTPS access configuration], some or all of the following properties will need to be passed to the test plugins: + +[source,xml,subs=attributes+] +---- + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire-plugin.version} + + + + ${javax.net.ssl.keyStoreType} + ${javax.net.ssl.keyStore} + ${javax.net.ssl.keyStorePassword} + ${javax.net.ssl.trustStore} + ${javax.net.ssl.trustStorePassword} + + + +---- + [[maven-configuration-profile]] === Building with a specific configuration profile From 66457bb7eedc039bf3bd5868d618f21c243c1cbf Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Thu, 3 Aug 2023 16:46:07 +0300 Subject: [PATCH 13/22] fix: quarkus cli initializes PluginManager for plugin commands (cherry picked from commit 927f7a851933443e8dd9662732874df87af71927) --- .../cli/src/main/java/io/quarkus/cli/QuarkusCli.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java index 3db8a4d8c3af2..40823d6b844d0 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java @@ -89,11 +89,14 @@ public int run(String... args) throws Exception { boolean interactiveMode = Arrays.stream(args).noneMatch(arg -> arg.equals("--cli-test")); Optional testDir = Arrays.stream(args).dropWhile(arg -> !arg.equals("--cli-test-dir")).skip(1).findFirst(); boolean helpCommand = Arrays.stream(args).anyMatch(arg -> arg.equals("--help")); + boolean pluginCommand = args.length >= 1 && (args[0].equals("plug") || args[0].equals("plugin")); + try { boolean existingCommand = checkMissingCommand(cmd, args).isEmpty(); - // If the command already exists and is not a help command (that lists subcommands), then just execute - // without dealing with plugins - if (existingCommand && !helpCommand) { + // If the command already exists and is not a help command (that lists subcommands) or plugin command, then just execute + // without dealing with plugins. + // The reason that we check if its a plugin command is that plugin commands need PluginManager initialization. + if (existingCommand && !helpCommand && !pluginCommand) { return cmd.execute(args); } PluginCommandFactory pluginCommandFactory = new PluginCommandFactory(output); From ddfb2f76307ac2fbf46c10e47cd7910cbdda7e4c Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Thu, 3 Aug 2023 16:46:34 +0300 Subject: [PATCH 14/22] fix: use correct quarkus prefix for jbang aliases (cherry picked from commit cc368cfa81053cb3cd8ed0b827b12f1f363a87ff) --- .../main/java/io/quarkus/cli/plugin/JBangCatalogService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/JBangCatalogService.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/JBangCatalogService.java index 1a0825ed4b2e7..c28ce62985983 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/JBangCatalogService.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/JBangCatalogService.java @@ -24,7 +24,7 @@ public class JBangCatalogService extends CatalogService { private final JBangSupport jbang; public JBangCatalogService(MessageWriter output) { - this(output, "quarkus", "quarkusio"); + this(output, "quarkus-", "quarkusio"); } public JBangCatalogService(MessageWriter output, String pluginPrefix, String fallbackCatalog, String... remoteCatalogs) { From 799c2d95ba909bc8b76acbe7ee74afdb1a876e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Fri, 4 Aug 2023 15:51:40 +0200 Subject: [PATCH 15/22] Fix OpenTelemetry svc name precedence and improve test covergae (cherry picked from commit c36cb73644834590fc34f3d89a8d891535ada668) --- .../OpenTelemetryServiceNameAppNameTest.java | 22 ++++++++++++++++ ... => OpenTelemetryServiceNameBaseTest.java} | 25 +++---------------- ...ryServiceNameCombinedResourceWinsTest.java | 22 ++++++++++++++++ ...tryServiceNameCombinedServiceWinsTest.java | 21 ++++++++++++++++ ...lemetryServiceNameNoResourceAttrsTest.java | 20 +++++++++++++++ ...nTelemetryServiceNameResourceAttrTest.java | 20 +++++++++++++++ .../runtime/tracing/TracerUtil.java | 16 ++++++------ 7 files changed, 115 insertions(+), 31 deletions(-) create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameAppNameTest.java rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{OpenTelemetryServiceNameTest.java => OpenTelemetryServiceNameBaseTest.java} (51%) create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameCombinedResourceWinsTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameCombinedServiceWinsTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameNoResourceAttrsTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameResourceAttrTest.java diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameAppNameTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameAppNameTest.java new file mode 100644 index 0000000000000..e6c3d688f0ba1 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameAppNameTest.java @@ -0,0 +1,22 @@ +package io.quarkus.opentelemetry.deployment; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; +import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; +import io.quarkus.test.QuarkusUnitTest; + +public class OpenTelemetryServiceNameAppNameTest extends OpenTelemetryServiceNameBaseTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(TestSpanExporter.class) + .addClass(TestSpanExporterProvider.class) + .addAsResource(new StringAsset("" + + "quarkus.otel.bsp.schedule.delay=50\n" + + "quarkus.application.name=" + SERVICE_NAME + "\n"), "application.properties")); +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameBaseTest.java similarity index 51% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameBaseTest.java index 363cb7864c049..1b9f3f3d6cee4 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameBaseTest.java @@ -11,41 +11,22 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.trace.data.SpanData; import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; -import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; -import io.quarkus.test.QuarkusUnitTest; import io.restassured.RestAssured; -import io.smallrye.config.SmallRyeConfig; -public class OpenTelemetryServiceNameTest { +public abstract class OpenTelemetryServiceNameBaseTest { - private static final String SERVICE_NAME = "FrankBullitt"; + protected static final String SERVICE_NAME = "FrankBullitt"; - @RegisterExtension - static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( - () -> ShrinkWrap.create(JavaArchive.class) - .addClass(TestSpanExporter.class) - .addClass(TestSpanExporterProvider.class) - .addAsResource("resource-config/application.properties", "application.properties") - .addAsResource( - "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", - "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) - .overrideRuntimeConfigKey("quarkus.otel.service.name", SERVICE_NAME); - - @Inject - SmallRyeConfig config; @Inject TestSpanExporter spanExporter; @Test - void testSvcNameHasPriorityOverAppNameAndResourceAttr() { + void testServiceName() { RestAssured.when() .get("/hello").then() .statusCode(200) diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameCombinedResourceWinsTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameCombinedResourceWinsTest.java new file mode 100644 index 0000000000000..cb40e024aeab7 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameCombinedResourceWinsTest.java @@ -0,0 +1,22 @@ +package io.quarkus.opentelemetry.deployment; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; +import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; +import io.quarkus.test.QuarkusUnitTest; + +public class OpenTelemetryServiceNameCombinedResourceWinsTest extends OpenTelemetryServiceNameBaseTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(TestSpanExporter.class) + .addClass(TestSpanExporterProvider.class)) + .overrideConfigKey("quarkus.application.name", "application-name-must-fail") + .overrideRuntimeConfigKey("quarkus.otel.bsp.schedule.delay", "50")// speed up test + .overrideRuntimeConfigKey("quarkus.otel.service.name", SERVICE_NAME) + .overrideRuntimeConfigKey("quarkus.otel.resource.attributes", "service.name=" + "attributes-must-fail"); +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameCombinedServiceWinsTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameCombinedServiceWinsTest.java new file mode 100644 index 0000000000000..39a3ad1aae3ba --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameCombinedServiceWinsTest.java @@ -0,0 +1,21 @@ +package io.quarkus.opentelemetry.deployment; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; +import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; +import io.quarkus.test.QuarkusUnitTest; + +public class OpenTelemetryServiceNameCombinedServiceWinsTest extends OpenTelemetryServiceNameBaseTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(TestSpanExporter.class) + .addClass(TestSpanExporterProvider.class)) + .overrideConfigKey("quarkus.application.name", "application-name-must-fail") + .overrideRuntimeConfigKey("quarkus.otel.bsp.schedule.delay", "50")// speed up test + .overrideRuntimeConfigKey("quarkus.otel.resource.attributes", "service.name=" + SERVICE_NAME); +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameNoResourceAttrsTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameNoResourceAttrsTest.java new file mode 100644 index 0000000000000..c693d9961fe86 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameNoResourceAttrsTest.java @@ -0,0 +1,20 @@ +package io.quarkus.opentelemetry.deployment; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; +import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; +import io.quarkus.test.QuarkusUnitTest; + +public class OpenTelemetryServiceNameNoResourceAttrsTest extends OpenTelemetryServiceNameBaseTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(TestSpanExporter.class) + .addClass(TestSpanExporterProvider.class)) + .overrideConfigKey("quarkus.otel.service.name", SERVICE_NAME) + .overrideRuntimeConfigKey("quarkus.otel.bsp.schedule.delay", "50");// speed up test +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameResourceAttrTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameResourceAttrTest.java new file mode 100644 index 0000000000000..c980f13dc641b --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameResourceAttrTest.java @@ -0,0 +1,20 @@ +package io.quarkus.opentelemetry.deployment; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; +import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; +import io.quarkus.test.QuarkusUnitTest; + +public class OpenTelemetryServiceNameResourceAttrTest extends OpenTelemetryServiceNameBaseTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(TestSpanExporter.class) + .addClass(TestSpanExporterProvider.class)) + .overrideRuntimeConfigKey("quarkus.otel.bsp.schedule.delay", "50")// speed up test + .overrideRuntimeConfigKey("quarkus.otel.resource.attributes", "service.name=" + SERVICE_NAME); +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtil.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtil.java index 3c9a6ff394bfc..84ebd239996fa 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtil.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtil.java @@ -15,20 +15,18 @@ private TracerUtil() { } public static Resource mapResourceAttributes(List resourceAttributes, String serviceName) { - if (resourceAttributes.isEmpty()) { - return Resource.empty(); + final AttributesBuilder attributesBuilder = Attributes.builder(); + + if (!resourceAttributes.isEmpty()) { + OpenTelemetryUtil + .convertKeyValueListToMap(resourceAttributes) + .forEach(attributesBuilder::put); } - AttributesBuilder attributesBuilder = Attributes.builder(); - var attrNameToValue = OpenTelemetryUtil.convertKeyValueListToMap(resourceAttributes); - // override both default (app name) and explicitly set resource attribute - // it needs to be done manually because OpenTelemetry correctly sets 'otel.service.name' - // to existing (incoming) resource, but customizer output replaces originally set service name if (serviceName != null) { - attrNameToValue.put(SERVICE_NAME.getKey(), serviceName); + attributesBuilder.put(SERVICE_NAME.getKey(), serviceName); } - attrNameToValue.forEach(attributesBuilder::put); return Resource.create(attributesBuilder.build()); } } From 63bff4e477a83f026ac1f33db5867a1a54757f8a Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 8 Aug 2023 09:47:07 +0200 Subject: [PATCH 16/22] Quartz: QuarkusMSSQLDelegate should extends MSSQLDelegate - and not HSQLDBDelegate (cherry picked from commit 5f1d0d487693e64c30003140a55862b31a9a5622) --- .../io/quarkus/quartz/runtime/jdbc/QuarkusMSSQLDelegate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/jdbc/QuarkusMSSQLDelegate.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/jdbc/QuarkusMSSQLDelegate.java index 569c040f3b255..4f6dcff474156 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/jdbc/QuarkusMSSQLDelegate.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/jdbc/QuarkusMSSQLDelegate.java @@ -6,7 +6,7 @@ import java.sql.ResultSet; import java.sql.SQLException; -public class QuarkusMSSQLDelegate extends org.quartz.impl.jdbcjobstore.HSQLDBDelegate { +public class QuarkusMSSQLDelegate extends org.quartz.impl.jdbcjobstore.MSSQLDelegate { /** * See the javadoc in {@link QuarkusObjectInputStream#resolveClass(ObjectStreamClass)} and * {@link DBDelegateUtils#getObjectFromInput(InputStream)} From 63c7aa577421e9e7bcb96bad38a892d3a4b86c98 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 10 Aug 2023 08:32:04 +0200 Subject: [PATCH 17/22] Higher timeouts for native CI --- .github/native-tests.json | 44 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/native-tests.json b/.github/native-tests.json index 34b7b248952f8..e5832132527c2 100644 --- a/.github/native-tests.json +++ b/.github/native-tests.json @@ -2,133 +2,133 @@ "include": [ { "category": "Main", - "timeout": 40, + "timeout": 45, "test-modules": "main", "os-name": "ubuntu-latest" }, { "category": "Data1", - "timeout": 90, + "timeout": 95, "test-modules": "jpa-h2, jpa-h2-embedded, jpa-mariadb, jpa-mssql, jpa-derby, jpa-without-entity, hibernate-orm-tenancy/datasource, hibernate-orm-tenancy/connection-resolver, hibernate-orm-tenancy/connection-resolver-legacy-qualifiers", "os-name": "ubuntu-latest" }, { "category": "Data2", - "timeout": 65, + "timeout": 70, "test-modules": "jpa, jpa-mapping-xml/legacy-app, jpa-mapping-xml/modern-app, jpa-mysql, jpa-db2, jpa-oracle", "os-name": "ubuntu-latest" }, { "category": "Data3", - "timeout": 70, + "timeout": 75, "test-modules": "flyway, hibernate-orm-panache, hibernate-orm-panache-kotlin, hibernate-orm-envers, liquibase, liquibase-mongodb", "os-name": "ubuntu-latest" }, { "category": "Data4", - "timeout": 55, + "timeout": 60, "test-modules": "mongodb-client, mongodb-devservices, mongodb-panache, mongodb-rest-data-panache, mongodb-panache-kotlin, redis-client, hibernate-orm-rest-data-panache", "os-name": "ubuntu-latest" }, { "category": "Data5", - "timeout": 65, + "timeout": 70, "test-modules": "jpa-postgresql, jpa-postgresql-withxml, narayana-stm, narayana-jta, reactive-pg-client, hibernate-reactive-postgresql, hibernate-orm-tenancy/schema", "os-name": "ubuntu-latest" }, { "category": "Data6", - "timeout": 90, + "timeout": 95, "test-modules": "elasticsearch-rest-client, elasticsearch-rest-high-level-client, elasticsearch-java-client, hibernate-search-orm-elasticsearch, hibernate-search-orm-elasticsearch-tenancy, hibernate-search-orm-opensearch, hibernate-search-orm-elasticsearch-coordination-outbox-polling", "os-name": "ubuntu-latest" }, { "category": "Data7", - "timeout": 80, + "timeout": 85, "test-modules": "reactive-oracle-client, reactive-mysql-client, reactive-db2-client, hibernate-reactive-db2, hibernate-reactive-mysql, hibernate-reactive-panache, hibernate-reactive-panache-kotlin", "os-name": "ubuntu-latest" }, { "category": "Amazon", - "timeout": 45, + "timeout": 50, "test-modules": "amazon-lambda, amazon-lambda-http", "os-name": "ubuntu-latest" }, { "category": "Messaging1", - "timeout": 110, + "timeout": 115, "test-modules": "kafka, kafka-ssl, kafka-sasl, kafka-avro-apicurio2, kafka-snappy, kafka-streams, reactive-messaging-kafka, kafka-oauth-keycloak", "os-name": "ubuntu-latest" }, { "category": "Messaging2", - "timeout": 70, + "timeout": 75, "test-modules": "reactive-messaging-amqp, reactive-messaging-rabbitmq, reactive-messaging-rabbitmq-dyn", "os-name": "ubuntu-latest" }, { "category": "Security1", - "timeout": 50, + "timeout": 60, "test-modules": "elytron-security-oauth2, elytron-security, elytron-security-jdbc, elytron-undertow, elytron-security-ldap, bouncycastle, bouncycastle-jsse, bouncycastle-fips", "os-name": "ubuntu-latest" }, { "category": "Security2", - "timeout": 70, + "timeout": 75, "test-modules": "oidc, oidc-code-flow, oidc-tenancy, oidc-client, oidc-client-reactive, oidc-token-propagation, oidc-wiremock, oidc-client-wiremock", "os-name": "ubuntu-latest" }, { "category": "Security3", - "timeout": 50, + "timeout": 55, "test-modules": "keycloak-authorization, smallrye-jwt-token-propagation, security-webauthn", "os-name": "ubuntu-latest" }, { "category": "Cache", - "timeout": 60, + "timeout": 65, "test-modules": "infinispan-cache-jpa, infinispan-client, cache, redis-cache", "os-name": "ubuntu-latest" }, { "category": "HTTP", - "timeout": 95, + "timeout": 110, "test-modules": "elytron-resteasy, resteasy-jackson, elytron-resteasy-reactive, resteasy-mutiny, resteasy-reactive-kotlin/standard, vertx, vertx-http, vertx-web, vertx-web-jackson, vertx-graphql, virtual-http, rest-client, rest-client-reactive, rest-client-reactive-stork, rest-client-reactive-multipart, websockets, management-interface, management-interface-auth", "os-name": "ubuntu-latest" }, { "category": "Misc1", - "timeout": 65, + "timeout": 80, "test-modules": "maven, jackson, jsonb, kotlin-serialization, rest-client-reactive-kotlin-serialization, quartz, qute, logging-min-level-unset, logging-min-level-set, simple with space", "os-name": "ubuntu-latest" }, { "category": "Misc2", - "timeout": 65, + "timeout": 70, "test-modules": "hibernate-validator, test-extension/tests, logging-gelf, bootstrap-config/application, mailer, native-config-profile, locales", "os-name": "ubuntu-latest" }, { "category": "Misc3", - "timeout": 75, + "timeout": 80, "test-modules": "kubernetes-client, openshift-client, kubernetes-service-binding-jdbc, smallrye-config, smallrye-graphql, smallrye-graphql-client, smallrye-metrics, smallrye-opentracing", "os-name": "ubuntu-latest" }, { "category": "Misc4", - "timeout": 120, + "timeout": 130, "test-modules": "picocli-native, gradle, micrometer-mp-metrics, micrometer-prometheus, logging-json, jaxp, jaxb, opentelemetry, opentelemetry-jdbc-instrumentation, webjars-locator", "os-name": "ubuntu-latest" }, { "category": "Spring", - "timeout": 55, + "timeout": 60, "test-modules": "spring-di, spring-web, spring-data-jpa, spring-boot-properties, spring-cloud-config-client, spring-data-rest", "os-name": "ubuntu-latest" }, { "category": "gRPC", - "timeout": 65, + "timeout": 70, "test-modules": "grpc-health, grpc-interceptors, grpc-mutual-auth, grpc-plain-text-gzip, grpc-plain-text-mutiny, grpc-proto-v2, grpc-streaming, grpc-tls", "os-name": "ubuntu-latest" }, From 8022a3f1b469c4e8d75fbc871c9dccb0cb90aa0f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 9 Aug 2023 18:28:58 +0200 Subject: [PATCH 18/22] Upgrade H2 to 2.2.220 (cherry picked from commit aa83e46d736fb9bb8fc2c3dafb53abdbc04fc106) --- bom/application/pom.xml | 2 +- .../hibernate/orm/runtime/config/DialectVersions.java | 2 +- .../io/quarkus/jdbc/h2/deployment/JDBCH2Processor.java | 10 ++++++++++ .../jdbc/h2/runtime/graalvm/DeleteFullTextLucene.java | 10 ++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DeleteFullTextLucene.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 2b2ebab72919b..31da8910a96e3 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -128,7 +128,7 @@ 4.1.5 9.2.1 2.3.2 - 2.1.214 + 2.2.220 42.6.0 3.1.4 8.0.30 diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/config/DialectVersions.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/config/DialectVersions.java index 365d42a3291a0..827437c97de90 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/config/DialectVersions.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/config/DialectVersions.java @@ -22,7 +22,7 @@ public static final class Defaults { public static final String ORACLE = "12"; // This must be aligned on the H2 version in the Quarkus BOM - public static final String H2 = "2.1.210"; + public static final String H2 = "2.2.220"; private Defaults() { } diff --git a/extensions/jdbc/jdbc-h2/deployment/src/main/java/io/quarkus/jdbc/h2/deployment/JDBCH2Processor.java b/extensions/jdbc/jdbc-h2/deployment/src/main/java/io/quarkus/jdbc/h2/deployment/JDBCH2Processor.java index f83906ba47abd..1f44be5e5f393 100644 --- a/extensions/jdbc/jdbc-h2/deployment/src/main/java/io/quarkus/jdbc/h2/deployment/JDBCH2Processor.java +++ b/extensions/jdbc/jdbc-h2/deployment/src/main/java/io/quarkus/jdbc/h2/deployment/JDBCH2Processor.java @@ -1,5 +1,7 @@ package io.quarkus.jdbc.h2.deployment; +import java.util.Set; + import io.quarkus.agroal.spi.JdbcDriverBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.processor.BuiltinScope; @@ -12,10 +14,12 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.RemovedResourceBuildItem; import io.quarkus.deployment.builditem.SslNativeConfigBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageEnableModule; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.jdbc.h2.runtime.H2AgroalConnectionConfigurer; +import io.quarkus.maven.dependency.ArtifactKey; public class JDBCH2Processor { @@ -62,4 +66,10 @@ NativeImageEnableModule registerNetModuleForNative() { //Compiling H2 to native requires activating the jdk.net module of the JDK return new NativeImageEnableModule("jdk.net"); } + + @BuildStep + void excludeNativeImageDirectives(BuildProducer removedResources) { + removedResources.produce(new RemovedResourceBuildItem(ArtifactKey.fromString("com.h2database:h2"), + Set.of("META-INF/native-image/reflect-config.json"))); + } } diff --git a/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DeleteFullTextLucene.java b/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DeleteFullTextLucene.java new file mode 100644 index 0000000000000..c5f16883dd5d6 --- /dev/null +++ b/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DeleteFullTextLucene.java @@ -0,0 +1,10 @@ +package io.quarkus.jdbc.h2.runtime.graalvm; + +import com.oracle.svm.core.annotate.Delete; +import com.oracle.svm.core.annotate.TargetClass; + +@Delete +@TargetClass(className = "org.h2.fulltext.FullTextLucene") +public final class DeleteFullTextLucene { + +} From 1b9a4c4f55e88f60ea2450aaa0b5ad3fed042c09 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Wed, 9 Aug 2023 22:44:46 -0300 Subject: [PATCH 19/22] Fix `jandex-gradle-plugin-version` in CDI Reference - Adds a link to https://plugins.gradle.org/plugin/org.kordamp.gradle.jandex - Fixes #35258 (cherry picked from commit 54502ea29640df67a660ae9cb0bc8eac5fd2e670) --- docs/src/main/asciidoc/_attributes.adoc | 2 +- docs/src/main/asciidoc/cdi-reference.adoc | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/_attributes.adoc b/docs/src/main/asciidoc/_attributes.adoc index 610ab5295bd07..53dc2eed166af 100644 --- a/docs/src/main/asciidoc/_attributes.adoc +++ b/docs/src/main/asciidoc/_attributes.adoc @@ -19,7 +19,7 @@ :kibana-image: ${kibana.image} :keycloak-docker-image: ${keycloak.docker.image} :jandex-version: ${jandex.version} -:jandex-gradle-plugin.version: ${jandex-gradle-plugin.version} +:jandex-gradle-plugin-version: ${jandex-gradle-plugin.version} :kotlin-version: ${kotlin.version} :grpc-version: ${grpc.version} :protoc-version: ${protoc.version} diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index f58090e488b6f..f91f1f401797c 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -82,7 +82,7 @@ plugins { id 'org.kordamp.gradle.jandex' version '{jandex-gradle-plugin-version}' } ---- - +You can find the latest plugin version in the https://plugins.gradle.org/plugin/org.kordamp.gradle.jandex[Gradle Plugin Portal] **** [role="secondary asciidoc-tabs-sync-gradle-kotlin"] @@ -95,6 +95,8 @@ plugins { id("org.kordamp.gradle.jandex") version '{jandex-gradle-plugin-version}' } ---- +You can find the latest plugin version in the https://plugins.gradle.org/plugin/org.kordamp.gradle.jandex[Gradle Plugin Portal] + **** From 3747c0d85b68ca33b58871d9cf3b2d4b3bb304a4 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Fri, 30 Jun 2023 17:42:50 +0200 Subject: [PATCH 20/22] An option to record and dump all used build time config options and a Maven goal that compares the recorded build config from the previous build to the current config before building an application Co-authored-by: Robert Stupp (cherry picked from commit 01eedeca0caf8d5b183c775dfc05f08626e92cd4) --- .../io/quarkus/deployment/CodeGenerator.java | 38 +++++ .../BuildTimeConfigurationReader.java | 31 +++- .../tracker/ConfigTrackingConfig.java | 103 +++++++++++ .../tracker/ConfigTrackingInterceptor.java | 89 ++++++++++ .../ConfigTrackingValueTransformer.java | 129 ++++++++++++++ .../tracker/ConfigTrackingWriter.java | 110 ++++++++++++ .../steps/ConfigGenerationBuildStep.java | 29 ++++ .../io/quarkus/runtime/util/HashUtil.java | 55 +++--- .../main/java/io/quarkus/maven/BuildMojo.java | 27 +-- .../quarkus/maven/QuarkusBootstrapMojo.java | 38 +++++ .../quarkus/maven/TrackConfigChangesMojo.java | 161 ++++++++++++++++++ docs/src/main/asciidoc/config-reference.adoc | 59 ++++++- .../java/io/quarkus/maven/it/PackageIT.java | 38 +++++ .../projects/config-tracking/pom.xml | 69 ++++++++ .../main/java/org/acme/GreetingResource.java | 16 ++ .../src/main/resources/application.properties | 3 + 16 files changed, 943 insertions(+), 52 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingConfig.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingInterceptor.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingValueTransformer.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingWriter.java create mode 100644 devtools/maven/src/main/java/io/quarkus/maven/TrackConfigChangesMojo.java create mode 100644 integration-tests/maven/src/test/resources-filtered/projects/config-tracking/pom.xml create mode 100644 integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/java/org/acme/GreetingResource.java create mode 100644 integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/resources/application.properties diff --git a/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java index bcc15ea7ec38f..b986340941039 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java @@ -24,6 +24,7 @@ import io.quarkus.bootstrap.prebuild.CodeGenException; import io.quarkus.deployment.codegen.CodeGenData; import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; +import io.quarkus.deployment.configuration.tracker.ConfigTrackingValueTransformer; import io.quarkus.deployment.dev.DevModeContext; import io.quarkus.deployment.dev.DevModeContext.ModuleInfo; import io.quarkus.maven.dependency.ResolvedDependency; @@ -185,6 +186,43 @@ public static boolean trigger(ClassLoader deploymentClassLoader, }); } + /** + * Initializes an application build time configuration and returns current values of properties + * passed in as {@code originalProperties}. + * + * @param appModel application model + * @param launchMode launch mode + * @param buildSystemProps build system (or project) properties + * @param deploymentClassLoader build classloader + * @param originalProperties properties to read from the initialized configuration + * @return current values of the passed in original properties + */ + public static Properties readCurrentConfigValues(ApplicationModel appModel, String launchMode, + Properties buildSystemProps, + QuarkusClassLoader deploymentClassLoader, Properties originalProperties) { + Config config = null; + try { + config = getConfig(appModel, LaunchMode.valueOf(launchMode), buildSystemProps, deploymentClassLoader); + } catch (CodeGenException e) { + throw new RuntimeException("Failed to load application configuration", e); + } + var valueTransformer = ConfigTrackingValueTransformer.newInstance(config); + final Properties currentValues = new Properties(originalProperties.size()); + for (var originalProp : originalProperties.entrySet()) { + var name = originalProp.getKey().toString(); + var currentValue = config.getConfigValue(name); + final String current = valueTransformer.transform(name, currentValue); + if (!originalProp.getValue().equals(current)) { + log.info("Option " + name + " has changed since the last build from " + + originalProp.getValue() + " to " + current); + } + if (current != null) { + currentValues.put(name, current); + } + } + return currentValues; + } + public static Config getConfig(ApplicationModel appModel, LaunchMode launchMode, Properties buildSystemProps, QuarkusClassLoader deploymentClassLoader) throws CodeGenException { final Map> unavailableConfigServices = getUnavailableConfigServices(appModel.getAppArtifact(), diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java index da0354bcdbc78..b2e8e8f9ae54b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java @@ -47,6 +47,7 @@ import io.quarkus.deployment.configuration.matching.FieldContainer; import io.quarkus.deployment.configuration.matching.MapContainer; import io.quarkus.deployment.configuration.matching.PatternMapBuilder; +import io.quarkus.deployment.configuration.tracker.ConfigTrackingInterceptor; import io.quarkus.deployment.configuration.type.ArrayOf; import io.quarkus.deployment.configuration.type.CollectionOf; import io.quarkus.deployment.configuration.type.ConverterType; @@ -124,6 +125,8 @@ private static List> collectConfigRoots(ClassLoader classLoader) throws final Set deprecatedProperties; final Set deprecatedRuntimeProperties; + final ConfigTrackingInterceptor buildConfigTracker; + /** * Initializes a new instance with located configuration root classes on the classpath * of a given classloader. @@ -242,6 +245,8 @@ private BuildTimeConfigurationReader(ClassLoader classLoader, final List clazz, @@ -408,11 +413,15 @@ public SmallRyeConfig initConfiguration(LaunchMode launchMode, Properties buildS for (ConfigClassWithPrefix mapping : getBuildTimeVisibleMappings()) { builder.withMapping(mapping.getKlass(), mapping.getPrefix()); } - return builder.build(); + + builder.withInterceptors(buildConfigTracker); + var config = builder.build(); + buildConfigTracker.configure(config); + return config; } public ReadResult readConfiguration(final SmallRyeConfig config) { - return SecretKeys.doUnlocked(() -> new ReadOperation(config).run()); + return SecretKeys.doUnlocked(() -> new ReadOperation(config, buildConfigTracker).run()); } private Set getDeprecatedProperties(Iterable rootDefinitions) { @@ -468,6 +477,7 @@ private void collectDeprecatedConfigItems(ClassMember classMember, Set d final class ReadOperation { final SmallRyeConfig config; + final ConfigTrackingInterceptor buildConfigTracker; final Set processedNames = new HashSet<>(); final Map, Object> objectsByClass = new HashMap<>(); @@ -477,8 +487,9 @@ final class ReadOperation { final Map> convByType = new HashMap<>(); - ReadOperation(final SmallRyeConfig config) { + ReadOperation(final SmallRyeConfig config, ConfigTrackingInterceptor buildConfigTracker) { this.config = config; + this.buildConfigTracker = buildConfigTracker; } ReadResult run() { @@ -684,6 +695,7 @@ ReadResult run() { .setRunTimeMappings(runTimeMappings) .setUnknownBuildProperties(unknownBuildProperties) .setDeprecatedRuntimeProperties(deprecatedRuntimeProperties) + .setBuildConfigTracker(buildConfigTracker) .createReadResult(); } @@ -1151,6 +1163,7 @@ public static final class ReadResult { final Set unknownBuildProperties; final Set deprecatedRuntimeProperties; + final ConfigTrackingInterceptor.ReadOptionsProvider readOptionsProvider; public ReadResult(final Builder builder) { this.objectsByClass = builder.getObjectsByClass(); @@ -1176,6 +1189,8 @@ public ReadResult(final Builder builder) { this.unknownBuildProperties = builder.getUnknownBuildProperties(); this.deprecatedRuntimeProperties = builder.deprecatedRuntimeProperties; + this.readOptionsProvider = builder.buildConfigTracker == null ? null + : builder.buildConfigTracker.getReadOptionsProvider(); } private static Map, RootDefinition> rootsToMap(Builder builder) { @@ -1276,6 +1291,10 @@ public Object requireObjectForClass(Class clazz) { return obj; } + public ConfigTrackingInterceptor.ReadOptionsProvider getReadOptionsProvider() { + return readOptionsProvider; + } + static class Builder { private Map, Object> objectsByClass; private Map allBuildTimeValues; @@ -1292,6 +1311,7 @@ static class Builder { private List runTimeMappings; private Set unknownBuildProperties; private Set deprecatedRuntimeProperties; + private ConfigTrackingInterceptor buildConfigTracker; Map, Object> getObjectsByClass() { return objectsByClass; @@ -1424,6 +1444,11 @@ Builder setDeprecatedRuntimeProperties(Set deprecatedRuntimeProperties) return this; } + Builder setBuildConfigTracker(ConfigTrackingInterceptor buildConfigTracker) { + this.buildConfigTracker = buildConfigTracker; + return this; + } + ReadResult createReadResult() { return new ReadResult(this); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingConfig.java new file mode 100644 index 0000000000000..6508e33023b89 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingConfig.java @@ -0,0 +1,103 @@ +package io.quarkus.deployment.configuration.tracker; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.quarkus.util.GlobUtil; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; + +/** + * Configuration options for application build time configuration usage tracking + * and dumping. + */ +@ConfigMapping(prefix = "quarkus.config-tracking") +@ConfigRoot(phase = ConfigPhase.BUILD_TIME) +public interface ConfigTrackingConfig { + + /** + * Whether configuration dumping is enabled + */ + @WithDefault("false") + boolean enabled(); + + /** + * Directory in which the configuration dump should be stored. + * If not configured the {@code .quarkus} directory under the project directory will be used. + */ + Optional directory(); + + /** + * File in which the configuration dump should be stored. If not configured, the {@link #filePrefix} and + * {@link #fileSuffix} will be used to generate the final file name. + * If the configured file path is absolute, the {@link #directory} option will be ignored. Otherwise, + * the path will be considered relative to the {@link #directory}. + */ + Optional file(); + + /** + * File name prefix. This option will be ignored in case {@link #file} is configured. + */ + @WithDefault("quarkus") + String filePrefix(); + + /** + * File name suffix. This option will be ignored in case {@link #file} is configured. + */ + @WithDefault("-config-dump") + String fileSuffix(); + + /** + * A list of config properties that should be excluded from the report. + * GLOB patterns could be used instead of property names. + */ + Optional> exclude(); + + /** + * Translates the value of {@link #exclude} to a list of {@link java.util.regex.Pattern}. + * + * @return list of patterns created from {@link #exclude} + */ + default List getExcludePatterns() { + return toPatterns(exclude()); + } + + /** + * A list of config properties whose values should be hashed in the report. + * The values will be hashed using SHA-512 algorithm. + * GLOB patterns could be used instead of property names. + */ + Optional> hashOptions(); + + /** + * Translates the value of {@link #hashOptions()} to a list of {@link java.util.regex.Pattern}. + * + * @return list of patterns created from {@link #hashOptions()} + */ + default List getHashOptionsPatterns() { + return toPatterns(hashOptions()); + } + + static List toPatterns(Optional> globs) { + if (globs.isEmpty()) { + return List.of(); + } + var list = globs.get(); + final List patterns = new ArrayList<>(list.size()); + for (var s : list) { + patterns.add(Pattern.compile(GlobUtil.toRegexPattern(s))); + } + return patterns; + } + + /** + * Whether to use a {@code ~} as an alias for user home directory in path values + */ + @WithDefault("true") + boolean useUserHomeAliasInPaths(); +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingInterceptor.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingInterceptor.java new file mode 100644 index 0000000000000..3e5f92866492a --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingInterceptor.java @@ -0,0 +1,89 @@ +package io.quarkus.deployment.configuration.tracker; + +import static io.smallrye.config.SecretKeys.doLocked; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import jakarta.annotation.Priority; + +import org.eclipse.microprofile.config.Config; + +import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; +import io.quarkus.runtime.LaunchMode; +import io.smallrye.config.ConfigSourceInterceptor; +import io.smallrye.config.ConfigSourceInterceptorContext; +import io.smallrye.config.ConfigValue; +import io.smallrye.config.Priorities; + +/** + * Build configuration interceptor that records all the configuration options + * and their values that are read during the build. + */ +@Priority(Priorities.APPLICATION) +public class ConfigTrackingInterceptor implements ConfigSourceInterceptor { + + /** + * A writer that persists collected configuration options and their values to a file + */ + public interface ConfigurationWriter { + void write(ConfigTrackingConfig config, BuildTimeConfigurationReader.ReadResult configReadResult, + LaunchMode launchMode, Path buildDirectory); + } + + /** + * Provides an immutable map of options that were read during the build. + */ + public interface ReadOptionsProvider { + + /** + * An immutable map of options read during the build. + * + * @return immutable map of options read during the build + */ + Map getReadOptions(); + } + + private boolean enabled; + // it's a String value map to be able to represent null (not configured) values + private Map readOptions = Map.of(); + private final ReadOptionsProvider readOptionsProvider = new ReadOptionsProvider() { + @Override + public Map getReadOptions() { + return Collections.unmodifiableMap(readOptions); + } + }; + + /** + * Initializes the configuration tracker + * + * @param config configuration instance + */ + public void configure(Config config) { + enabled = config.getValue("quarkus.config-tracking.enabled", boolean.class); + if (enabled) { + readOptions = new ConcurrentHashMap<>(); + } + } + + @Override + public ConfigValue getValue(ConfigSourceInterceptorContext context, String name) { + if (!enabled) { + return context.proceed(name); + } + final ConfigValue configValue = doLocked(() -> context.proceed(name)); + readOptions.put(name, ConfigTrackingValueTransformer.asString(configValue)); + return configValue; + } + + /** + * Read options orvipder. + * + * @return read options provider + */ + public ReadOptionsProvider getReadOptionsProvider() { + return readOptionsProvider; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingValueTransformer.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingValueTransformer.java new file mode 100644 index 0000000000000..7c098a6b722cf --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingValueTransformer.java @@ -0,0 +1,129 @@ +package io.quarkus.deployment.configuration.tracker; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.StringJoiner; +import java.util.regex.Pattern; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigValue; + +import io.quarkus.bootstrap.util.PropertyUtils; +import io.smallrye.config.SmallRyeConfig; + +/** + * Transforms configuration values before they are written to a file + */ +public class ConfigTrackingValueTransformer { + + private static final String NOT_CONFIGURED = "quarkus.config-tracking:not-configured"; + private static final String PATH_ELEMENT_SEPARATOR = "/"; + private static final String USER_HOME_DIR_ALIAS = "~"; + + private static volatile MessageDigest SHA512; + + private static MessageDigest getSHA512() { + if (SHA512 == null) { + try { + SHA512 = MessageDigest.getInstance("SHA-512"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + return SHA512; + } + + public static ConfigTrackingValueTransformer newInstance(Config config) { + return new ConfigTrackingValueTransformer( + config.unwrap(SmallRyeConfig.class).getConfigMapping(ConfigTrackingConfig.class)); + } + + public static ConfigTrackingValueTransformer newInstance(ConfigTrackingConfig config) { + return new ConfigTrackingValueTransformer(config); + } + + /** + * Returns a non-null string value for a given {@link org.eclipse.microprofile.config.ConfigValue} instance. + * + * @param value configuration value + * @return non-null string value for a given {@link org.eclipse.microprofile.config.ConfigValue} instance + */ + public static String asString(ConfigValue value) { + return value == null ? NOT_CONFIGURED : value.getValue(); + } + + private final String userHomeDir; + private final List hashOptionsPatterns; + + private ConfigTrackingValueTransformer(ConfigTrackingConfig config) { + userHomeDir = config.useUserHomeAliasInPaths() ? PropertyUtils.getUserHome() : null; + hashOptionsPatterns = config.getHashOptionsPatterns(); + } + + /** + * Returns a string value that can be persisted to file. + * + * @param name option name + * @param value configuration value + * @return string value that can be persisted to file + */ + public String transform(String name, ConfigValue value) { + return value == null ? NOT_CONFIGURED : transform(name, value.getValue()); + } + + /** + * Returns a string value that can be persisted to file. + * + * @param name option name + * @param original configuration value + * @return string value that can be persisted to file + */ + public String transform(String name, String original) { + if (original == null) { + return NOT_CONFIGURED; + } + + for (Pattern pattern : hashOptionsPatterns) { + if (pattern.matcher(name).matches()) { + return sha512(original); + } + } + + // replace user home path with an alias + if (userHomeDir != null && original.startsWith(userHomeDir)) { + var relativePath = original.substring(userHomeDir.length()); + if (relativePath.isEmpty()) { + return USER_HOME_DIR_ALIAS; + } + if (File.separator.equals(PATH_ELEMENT_SEPARATOR)) { + return USER_HOME_DIR_ALIAS + relativePath; + } + final StringJoiner joiner = new StringJoiner("/"); + joiner.add(USER_HOME_DIR_ALIAS); + var path = Path.of(relativePath); + for (int i = 0; i < path.getNameCount(); ++i) { + joiner.add(path.getName(i).toString()); + } + return joiner.toString(); + } + + return original; + } + + public static String sha512(String value) { + return sha512(value.getBytes(StandardCharsets.UTF_8)); + } + + public static String sha512(byte[] value) { + final byte[] digest = getSHA512().digest(value); + final StringBuilder sb = new StringBuilder(40); + for (int i = 0; i < digest.length; ++i) { + sb.append(Integer.toHexString((digest[i] & 0xFF) | 0x100).substring(1, 3)); + } + return sb.toString(); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingWriter.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingWriter.java new file mode 100644 index 0000000000000..20c51265c5032 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingWriter.java @@ -0,0 +1,110 @@ +package io.quarkus.deployment.configuration.tracker; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; +import io.quarkus.runtime.LaunchMode; + +public class ConfigTrackingWriter { + + /** + * Checks whether a given configuration option matches at least one of the patterns. + * If the list of patterns is empty, the method will return false. + * + * @param name configuration option name + * @param patterns a list of name patterns + * @return true in case the option name matches at least one of the patterns, otherwise - false + */ + private static boolean matches(String name, List patterns) { + for (var pattern : patterns) { + if (pattern.matcher(name).matches()) { + return true; + } + } + return false; + } + + /** + * Configuration writer that will persist collected configuration options and their values + * to a file. + */ + public static void write(Map readOptions, ConfigTrackingConfig config, + BuildTimeConfigurationReader.ReadResult configReadResult, + LaunchMode launchMode, Path buildDirectory) { + if (!config.enabled()) { + return; + } + + Path file = config.file().orElse(null); + if (file == null) { + final Path dir = config.directory().orElseGet(() -> (buildDirectory.getParent() == null + ? buildDirectory + : buildDirectory.getParent()).resolve(".quarkus")); + file = dir + .resolve(config.filePrefix() + "-" + launchMode.getDefaultProfile() + config.fileSuffix()); + } else if (!file.isAbsolute()) { + file = config.directory().orElse(buildDirectory).resolve(file); + } + + if (file.getParent() != null) { + try { + Files.createDirectories(file.getParent()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + final List excludePatterns = config.getExcludePatterns(); + final ConfigTrackingValueTransformer valueTransformer = ConfigTrackingValueTransformer.newInstance(config); + + final Map allBuildTimeValues = configReadResult.getAllBuildTimeValues(); + final Map buildTimeRuntimeValues = configReadResult.getBuildTimeRunTimeValues(); + try (BufferedWriter writer = Files.newBufferedWriter(file)) { + final List names = new ArrayList<>(readOptions.size()); + for (var name : readOptions.keySet()) { + if ((allBuildTimeValues.containsKey(name) || buildTimeRuntimeValues.containsKey(name)) + && !matches(name, excludePatterns)) { + names.add(name); + } + } + Collections.sort(names); + for (String name : names) { + var value = valueTransformer.transform(name, readOptions.get(name)); + write(writer, name, value); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Writes a config option with its value to the target writer, + * possibly applying some transformations, such as character escaping + * prior to writing. + * + * @param writer target writer + * @param name option name + * @param value option value + * @throws IOException in case of a failure + */ + public static void write(Writer writer, String name, String value) throws IOException { + if (value != null) { + // escape the backslash before persisting + value = value.replace("\\", "\\\\"); + writer.write(name); + writer.write("="); + writer.write(value); + writer.write(System.lineSeparator()); + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java index 48bd549b6edd6..1b118239d4991 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java @@ -10,6 +10,7 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; +import java.io.Closeable; import java.io.IOException; import java.lang.reflect.Modifier; import java.net.URI; @@ -51,6 +52,7 @@ import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.LiveReloadBuildItem; +import io.quarkus.deployment.builditem.QuarkusBuildCloseablesBuildItem; import io.quarkus.deployment.builditem.RunTimeConfigBuilderBuildItem; import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; import io.quarkus.deployment.builditem.StaticInitConfigBuilderBuildItem; @@ -60,6 +62,10 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; import io.quarkus.deployment.configuration.RunTimeConfigurationGenerator; +import io.quarkus.deployment.configuration.tracker.ConfigTrackingConfig; +import io.quarkus.deployment.configuration.tracker.ConfigTrackingWriter; +import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem; +import io.quarkus.deployment.pkg.builditem.BuildSystemTargetBuildItem; import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.gizmo.ClassCreator; @@ -480,6 +486,29 @@ void warnDifferentProfileUsedBetweenBuildAndRunTime(ConfigRecorder configRecorde configRecorder.handleNativeProfileChange(config.getProfiles()); } + @BuildStep(onlyIf = IsNormal.class) + void persistReadConfigOptions(BuildProducer dummy, + QuarkusBuildCloseablesBuildItem closeables, + LaunchModeBuildItem launchModeBuildItem, + BuildSystemTargetBuildItem buildSystemTargetBuildItem, + ConfigurationBuildItem configBuildItem, + ConfigTrackingConfig configTrackingConfig) { + var readOptionsProvider = configBuildItem.getReadResult().getReadOptionsProvider(); + if (readOptionsProvider != null) { + closeables.add(new Closeable() { + @Override + public void close() throws IOException { + ConfigTrackingWriter.write( + readOptionsProvider.getReadOptions(), + configTrackingConfig, + configBuildItem.getReadResult(), + launchModeBuildItem.getLaunchMode(), + buildSystemTargetBuildItem.getOutputDirectory()); + } + }); + } + } + private String appendProfileToFilename(Path path, String activeProfile) { String pathWithoutExtension = getPathWithoutExtension(path); return String.format("%s-%s.%s", pathWithoutExtension, activeProfile, getFileExtension(path)); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/util/HashUtil.java b/core/runtime/src/main/java/io/quarkus/runtime/util/HashUtil.java index 4b42a4780ef1a..e70a7c6d65ddc 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/util/HashUtil.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/util/HashUtil.java @@ -6,6 +6,20 @@ public final class HashUtil { + private static MessageDigest getMessageDigest(String alg) { + try { + return MessageDigest.getInstance(alg); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + + private static void toHex(byte[] digest, StringBuilder sb) { + for (int i = 0; i < digest.length; ++i) { + sb.append(Integer.toHexString((digest[i] & 0xFF) | 0x100).substring(1, 3)); + } + } + private HashUtil() { } @@ -14,17 +28,10 @@ public static String sha1(String value) { } public static String sha1(byte[] value) { - try { - MessageDigest md = MessageDigest.getInstance("SHA-1"); - byte[] digest = md.digest(value); - StringBuilder sb = new StringBuilder(40); - for (int i = 0; i < digest.length; ++i) { - sb.append(Integer.toHexString((digest[i] & 0xFF) | 0x100).substring(1, 3)); - } - return sb.toString(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(e); - } + final byte[] digest = getMessageDigest("SHA-1").digest(value); + var sb = new StringBuilder(40); + toHex(digest, sb); + return sb.toString(); } public static String sha256(String value) { @@ -32,16 +39,20 @@ public static String sha256(String value) { } public static String sha256(byte[] value) { - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(value); - StringBuilder sb = new StringBuilder(40); - for (int i = 0; i < digest.length; ++i) { - sb.append(Integer.toHexString((digest[i] & 0xFF) | 0x100).substring(1, 3)); - } - return sb.toString(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(e); - } + final byte[] digest = getMessageDigest("SHA-256").digest(value); + var sb = new StringBuilder(40); + toHex(digest, sb); + return sb.toString(); + } + + public static String sha512(String value) { + return sha512(value.getBytes(StandardCharsets.UTF_8)); + } + + public static String sha512(byte[] value) { + final byte[] digest = getMessageDigest("SHA-512").digest(value); + var sb = new StringBuilder(128); + toHex(digest, sb); + return sb.toString(); } } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java index bbe090b33678c..0c7146ddb1678 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java @@ -9,9 +9,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; @@ -21,7 +19,6 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; -import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; import org.eclipse.aether.repository.RemoteRepository; @@ -38,10 +35,6 @@ @Mojo(name = "build", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, threadSafe = true) public class BuildMojo extends QuarkusBootstrapMojo { - static final String PACKAGE_TYPE_PROP = "quarkus.package.type"; - static final String NATIVE_PROFILE_NAME = "native"; - static final String NATIVE_PACKAGE_TYPE = "native"; - @Component MavenProjectHelper projectHelper; @@ -125,14 +118,7 @@ protected void doExecute() throws MojoExecutionException { // Essentially what this does is to enable the native package type even if a different package type is set // in application properties. This is done to preserve what users expect to happen when // they execute "mvn package -Dnative" even if quarkus.package.type has been set in application.properties - if (!System.getProperties().containsKey(PACKAGE_TYPE_PROP) - && isNativeProfileEnabled(mavenProject())) { - Object packageTypeProp = mavenProject().getProperties().get(PACKAGE_TYPE_PROP); - String packageType = NATIVE_PACKAGE_TYPE; - if (packageTypeProp != null) { - packageType = packageTypeProp.toString(); - } - System.setProperty(PACKAGE_TYPE_PROP, packageType); + if (!setPackageTypeSystemPropertyIfNativeProfileEnabled()) { propertiesToClear.add(PACKAGE_TYPE_PROP); } @@ -193,17 +179,6 @@ && isNativeProfileEnabled(mavenProject())) { } } - boolean isNativeProfileEnabled(MavenProject mavenProject) { - // gotcha: mavenProject.getActiveProfiles() does not always contain all active profiles (sic!), - // but getInjectedProfileIds() does (which has to be "flattened" first) - Stream activeProfileIds = mavenProject.getInjectedProfileIds().values().stream().flatMap(List::stream); - if (activeProfileIds.anyMatch(NATIVE_PROFILE_NAME::equalsIgnoreCase)) { - return true; - } - // recurse into parent (if available) - return Optional.ofNullable(mavenProject.getParent()).map(this::isNativeProfileEnabled).orElse(false); - } - @Override public void setLog(Log log) { super.setLog(log); 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 f303f390b8ec4..4bbf1b902d4b4 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java @@ -6,7 +6,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; +import java.util.stream.Stream; import org.apache.maven.AbstractMavenLifecycleParticipant; import org.apache.maven.execution.MavenSession; @@ -34,6 +36,10 @@ public abstract class QuarkusBootstrapMojo extends AbstractMojo { static final String CLOSE_BOOTSTRAPPED_APP = "closeBootstrappedApp"; + static final String NATIVE_PACKAGE_TYPE = "native"; + static final String NATIVE_PROFILE_NAME = "native"; + static final String PACKAGE_TYPE_PROP = "quarkus.package.type"; + @Component protected QuarkusBootstrapProvider bootstrapProvider; @@ -293,4 +299,36 @@ protected CuratedApplication bootstrapApplication(LaunchMode mode) throws MojoEx protected Properties getBuildSystemProperties(boolean quarkusOnly) throws MojoExecutionException { return bootstrapProvider.bootstrapper(this).getBuildSystemProperties(this, quarkusOnly); } + + /** + * Essentially what this does is to enable the native package type even if a different package type is set + * in application properties. This is done to preserve what users expect to happen when + * they execute "mvn package -Dnative" even if quarkus.package.type has been set in application.properties + * + * @return true if the package type system property was set, otherwise - false + */ + protected boolean setPackageTypeSystemPropertyIfNativeProfileEnabled() { + if (!System.getProperties().containsKey(PACKAGE_TYPE_PROP) + && isNativeProfileEnabled(mavenProject())) { + Object packageTypeProp = mavenProject().getProperties().get(PACKAGE_TYPE_PROP); + String packageType = NATIVE_PACKAGE_TYPE; + if (packageTypeProp != null) { + packageType = packageTypeProp.toString(); + } + System.setProperty(PACKAGE_TYPE_PROP, packageType); + return true; + } + return false; + } + + private boolean isNativeProfileEnabled(MavenProject mavenProject) { + // gotcha: mavenProject.getActiveProfiles() does not always contain all active profiles (sic!), + // but getInjectedProfileIds() does (which has to be "flattened" first) + Stream activeProfileIds = mavenProject.getInjectedProfileIds().values().stream().flatMap(List::stream); + if (activeProfileIds.anyMatch(NATIVE_PROFILE_NAME::equalsIgnoreCase)) { + return true; + } + // recurse into parent (if available) + return Optional.ofNullable(mavenProject.getParent()).map(this::isNativeProfileEnabled).orElse(false); + } } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/TrackConfigChangesMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/TrackConfigChangesMojo.java new file mode 100644 index 0000000000000..e721cf89e80c7 --- /dev/null +++ b/devtools/maven/src/main/java/io/quarkus/maven/TrackConfigChangesMojo.java @@ -0,0 +1,161 @@ +package io.quarkus.maven; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; + +import org.apache.maven.plugin.MojoExecutionException; +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.classloading.QuarkusClassLoader; +import io.quarkus.bootstrap.model.ApplicationModel; +import io.quarkus.deployment.configuration.tracker.ConfigTrackingWriter; +import io.quarkus.runtime.LaunchMode; + +/** + * Maven goal that is executed before the {@link BuildMojo}. + * The goal looks for a file that contains build time configuration options read during the previous build. + * If that file exists, the goal will check whether the configuration options used during the previous build + * have changed in the current configuration and will persist their current values to another file, so that + * both configuration files could be compared by tools caching build goal outcomes to check whether the previous + * outcome of the {@link BuildMojo} needs to be rebuilt. + */ +@Mojo(name = "track-config-changes", defaultPhase = LifecyclePhase.PROCESS_RESOURCES, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, threadSafe = true) +public class TrackConfigChangesMojo extends QuarkusBootstrapMojo { + + /** + * Skip the execution of this mojo + */ + @Parameter(defaultValue = "false", property = "quarkus.track-config-changes.skip") + boolean skip = false; + + @Parameter(property = "launchMode") + String mode; + + @Parameter(property = "quarkus.track-config-changes.outputDirectory", defaultValue = "${project.build.directory}") + File outputDirectory; + + @Parameter(property = "quarkus.track-config-changes.outputFile", required = false) + File outputFile; + + @Parameter(property = "quarkus.recorded-build-config.directory", defaultValue = "${basedir}/.quarkus") + File recordedBuildConfigDirectory; + + @Parameter(property = "quarkus.recorded-build-config.file", required = false) + File recordedBuildConfigFile; + + @Override + protected boolean beforeExecute() throws MojoExecutionException, MojoFailureException { + if (skip) { + getLog().info("Skipping config dump"); + return false; + } + return true; + } + + @Override + protected void doExecute() throws MojoExecutionException, MojoFailureException { + final String lifecyclePhase = mojoExecution.getLifecyclePhase(); + if (mode == null) { + if (lifecyclePhase == null) { + mode = "NORMAL"; + } else { + mode = lifecyclePhase.contains("test") ? "TEST" : "NORMAL"; + } + } + final LaunchMode launchMode = LaunchMode.valueOf(mode); + if (getLog().isDebugEnabled()) { + getLog().debug("Bootstrapping Quarkus application in mode " + launchMode); + } + + Path targetFile; + if (outputFile == null) { + targetFile = outputDirectory.toPath() + .resolve("quarkus-" + launchMode.getDefaultProfile() + "-config-check"); + } else if (outputFile.isAbsolute()) { + targetFile = outputFile.toPath(); + } else { + targetFile = outputDirectory.toPath().resolve(outputFile.toPath()); + } + + Path compareFile; + if (this.recordedBuildConfigFile == null) { + compareFile = recordedBuildConfigDirectory.toPath() + .resolve("quarkus-" + launchMode.getDefaultProfile() + "-config-dump"); + } else if (this.recordedBuildConfigFile.isAbsolute()) { + compareFile = this.recordedBuildConfigFile.toPath(); + } else { + compareFile = recordedBuildConfigDirectory.toPath().resolve(this.recordedBuildConfigFile.toPath()); + } + + if (!Files.exists(compareFile)) { + getLog().info(compareFile + " not found"); + return; + } + final Properties compareProps = new Properties(); + try (BufferedReader reader = Files.newBufferedReader(compareFile)) { + compareProps.load(reader); + } catch (IOException e) { + throw new RuntimeException("Failed to read " + compareFile, e); + } + + CuratedApplication curatedApplication = null; + QuarkusClassLoader deploymentClassLoader = null; + final ClassLoader originalCl = Thread.currentThread().getContextClassLoader(); + Properties actualProps; + final boolean clearPackageTypeSystemProperty = setPackageTypeSystemPropertyIfNativeProfileEnabled(); + try { + curatedApplication = bootstrapApplication(launchMode); + deploymentClassLoader = curatedApplication.createDeploymentClassLoader(); + Thread.currentThread().setContextClassLoader(deploymentClassLoader); + + final Class codeGenerator = deploymentClassLoader.loadClass("io.quarkus.deployment.CodeGenerator"); + final Method dumpConfig = codeGenerator.getMethod("readCurrentConfigValues", ApplicationModel.class, String.class, + Properties.class, QuarkusClassLoader.class, Properties.class); + actualProps = (Properties) dumpConfig.invoke(null, curatedApplication.getApplicationModel(), + launchMode.name(), getBuildSystemProperties(true), + deploymentClassLoader, compareProps); + } catch (Exception any) { + throw new MojoExecutionException("Failed to bootstrap Quarkus application", any); + } finally { + System.clearProperty(PACKAGE_TYPE_PROP); + Thread.currentThread().setContextClassLoader(originalCl); + if (deploymentClassLoader != null) { + deploymentClassLoader.close(); + } + } + + final List names = new ArrayList<>(actualProps.stringPropertyNames()); + Collections.sort(names); + + final Path outputDir = targetFile.getParent(); + if (outputDir != null && !Files.exists(outputDir)) { + try { + Files.createDirectories(outputDir); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + try (BufferedWriter writer = Files.newBufferedWriter(targetFile)) { + for (var name : names) { + ConfigTrackingWriter.write(writer, name, actualProps.getProperty(name)); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/docs/src/main/asciidoc/config-reference.adoc b/docs/src/main/asciidoc/config-reference.adoc index 41664ad7df60f..8f26a9b2479c5 100644 --- a/docs/src/main/asciidoc/config-reference.adoc +++ b/docs/src/main/asciidoc/config-reference.adoc @@ -621,7 +621,7 @@ extensions. Therefore, the `quarkus.` prefix should **never** be used for applic === Build Time configuration -Some Quarkus configurations only take effect during build time, meaning is not possible to change them at runtime. These +Some Quarkus configurations only take effect during build time, meaning it is not possible to change them at runtime. These configurations are still available at runtime but as read-only and have no effect in Quarkus behaviour. A change to any of these configurations requires a rebuild of the application itself to reflect changes of such properties. @@ -635,6 +635,63 @@ application behaviour at runtime. If you are in the rare situation that you need to change the build time configuration after your application is built, then check out how xref:reaugmentation.adoc[re-augmentation] can be used to rebuild the augmentation output for a different build time configuration. +== Tracking effective build time configuration used at build time + +Given that configuration sources usually provide more options than actually used during the build, it might be useful to know which configuration options have actually been used during a Quarkus build process. + +=== Dumping build time configuration options read during the build + +Setting `quarkus.config-tracking.enabled` to `true` will enable a configuration interceptor that will record every configuration option that was read during the build process along with their values. The resulting report will be stored in `${project.basedir}/.quarkus/quarkus-prod-config-dump` by default. The target file could be configured using the following options: + +* `quarkus.config-tracking.directory` - directory in which the configuration dump should be stored, the default is `${project.basedir}/.quarkus` +* `quarkus.config-tracking.file-prefix` - file name prefix, the default value is `quarkus` +* `quarkus.config-tracking.file-suffix` - file name suffix, the default value is `-config-dump` +* `quarkus.config-tracking.file` - path to a file in which the configuration dump should be stored. This option supersedes the `file-prefix` and `file-suffix` options. Also supersedes the value of `quarkus.config-tracking.directory`, unless the value is a relative path. + +The `prod` part of the `quarkus-prod-config-dump` file name refers to the Quarkus build mode, indicating that the dump was taken for the production build. + +The reason `${project.basedir}/.quarkus` directory was chosen as the default location was to make it easy to track build time configuration changes between builds and use that as an indicator to build output caching tools (such as https://maven.apache.org/extensions/maven-build-cache-extension/[Apache Maven Build Cache] and https://gradle.com/gradle-enterprise-solutions/build-cache/[Gradle Enterprise Build Cache]) whether the application binary has to be re-built. + +==== Filtering configuration options + +Configuration tracker could be instructed to exclude some of the options from the report by configuring `quarkus.config-tracking.exclude` with a comma-separated list of configuration option names that should be filtered out. + +==== Path values + +Configuration options with *absolute* path values that begin with a user home directory are, by default, transformed with Unix home directory alias '~' replacing the user home directory part and using `/` as a path element separator. + +This transformation can be disabled by setting `quarkus.config-tracking.use-user-home-alias-in-paths` to `false`. + +==== Hashing recorded configuration values + +Configuration values can be hashed using `SHA-512` algorithm before they are written to a file. Configuration option names whose values should be hashed can be configured in `quarkus.config-tracking.hash-options` as a comma separated list. + +=== Tracking build time configuration changes between builds + +While `quarkus.config-tracking.enabled` enables effective build time configuration report generation, there is also a way to check whether the values stored in that report have changed before the next build of the project is launched. + +Maven projects could add the following goal to their `quarkus-maven-plugin` configuration: +[source,xml] +---- + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + track-prod-config-changes + process-resources + + track-config-changes + + + +---- + +The `track-config-changes` goal looks for `${project.basedir}/.quarkus/quarkus-prod-config-dump` (file name and directory are configurable) and, if the file already exists, checks whether the values stored in the config dump have changed. +It will log the changed options and save the current values of each of the options present in `${project.basedir}/.quarkus/quarkus-prod-config-dump` in `${project.basedir}/target/quarkus-prod-config.check` (the target file name and location can be configured). If the build time configuration has not changed since the last build both `${project.basedir}/.quarkus/quarkus-prod-config-dump` and `${project.basedir}/.quarkus/quarkus-prod-config-dump` will be identical. + [[additional-information]] == Additional Information diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java index 86e576f03b12a..2859505ab1f51 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -27,6 +28,7 @@ import io.quarkus.maven.it.verifier.MavenProcessInvocationResult; import io.quarkus.maven.it.verifier.RunningInvoker; +import io.quarkus.runtime.util.HashUtil; @DisableForNative public class PackageIT extends MojoTestBase { @@ -34,6 +36,42 @@ public class PackageIT extends MojoTestBase { private RunningInvoker running; private File testDir; + @Test + public void testConfigTracking() throws Exception { + testDir = initProject("projects/config-tracking"); + running = new RunningInvoker(testDir, false); + var configDump = new File(new File(testDir, ".quarkus"), "quarkus-prod-config-dump"); + var configCheck = new File(new File(testDir, "target"), "quarkus-prod-config-check"); + + // initial build that generates .quarkus/quarkus-prod-config-dump + var result = running.execute(List.of("clean package -DskipTests"), Map.of()); + assertThat(result.getProcess().waitFor()).isEqualTo(0); + assertThat(configDump).exists(); + assertThat(configCheck).doesNotExist(); + + // rebuild and compare the files + result = running.execute(List.of("package -DskipTests"), Map.of()); + assertThat(result.getProcess().waitFor()).isEqualTo(0); + assertThat(configDump).exists(); + assertThat(configCheck).exists(); + assertThat(configDump).hasSameTextualContentAs(configCheck); + + var props = new Properties(); + try (BufferedReader reader = Files.newBufferedReader(configDump.toPath())) { + props.load(reader); + } + assertThat(props).containsEntry("quarkus.application.name", HashUtil.sha512("code-with-quarkus")); + + assertThat(props).doesNotContainKey("quarkus.platform.group-id"); + for (var name : props.stringPropertyNames()) { + assertThat(name).doesNotStartWith("quarkus.test."); + } + + result = running.execute(List.of("package -DskipTests -Dquarkus.package.type=uber-jar"), Map.of()); + assertThat(result.getProcess().waitFor()).isEqualTo(0); + assertThat(running.log()).contains("Option quarkus.package.type has changed since the last build from jar to uber-jar"); + } + @Test public void testPluginClasspathConfig() throws Exception { testDir = initProject("projects/test-plugin-classpath-config"); diff --git a/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/pom.xml new file mode 100644 index 0000000000000..434437b4d2610 --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + org.acme + code-with-quarkus + 1.0.0-SNAPSHOT + + ${compiler-plugin.version} + ${maven.compiler.release} + UTF-8 + UTF-8 + quarkus-bom + io.quarkus + ${project.version} + + + + + \${quarkus.platform.group-id} + \${quarkus.platform.artifact-id} + \${quarkus.platform.version} + pom + import + + + + + + io.quarkus + quarkus-resteasy-reactive + + + + + + \${quarkus.platform.group-id} + quarkus-maven-plugin + \${quarkus.platform.version} + true + + + track-prod-config-changes + process-resources + + track-config-changes + + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + \${compiler-plugin.version} + + + -parameters + + + + + + \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/java/org/acme/GreetingResource.java b/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/java/org/acme/GreetingResource.java new file mode 100644 index 0000000000000..6938062ec8ff7 --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/java/org/acme/GreetingResource.java @@ -0,0 +1,16 @@ +package org.acme; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/hello") +public class GreetingResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "Hello from RESTEasy Reactive"; + } +} diff --git a/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/resources/application.properties b/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/resources/application.properties new file mode 100644 index 0000000000000..ff43978c7f6ca --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/resources/application.properties @@ -0,0 +1,3 @@ +quarkus.config-tracking.enabled=true +quarkus.config-tracking.hash-options=quarkus.application.* +quarkus.config-tracking.exclude=quarkus.test.*,quarkus.platform.group-id \ No newline at end of file From b48be6c354968131e82e89f526cd76077335ddc8 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Fri, 4 Aug 2023 14:29:19 +0200 Subject: [PATCH 21/22] Pass Maven user settings when initializing artifact resolver (cherry picked from commit 3306eb930024c8726c53e02e4532805b33e9b369) --- .../src/main/java/io/quarkus/maven/DependencyTreeMojo.java | 5 +++++ devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java | 1 + .../maven/src/main/java/io/quarkus/maven/GoOfflineMojo.java | 5 +++++ .../main/java/io/quarkus/maven/QuarkusBootstrapProvider.java | 3 +++ .../main/java/io/quarkus/maven/QuarkusProjectMojoBase.java | 4 ++++ .../java/io/quarkus/maven/QuarkusProjectStateMojoBase.java | 1 + 6 files changed, 19 insertions(+) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DependencyTreeMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DependencyTreeMojo.java index a990e5d8ccdef..22891e8f44de4 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DependencyTreeMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DependencyTreeMojo.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.function.Consumer; +import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -37,6 +38,9 @@ public class DependencyTreeMojo extends AbstractMojo { @Parameter(defaultValue = "${project}", readonly = true, required = true) protected MavenProject project; + @Parameter(defaultValue = "${session}", readonly = true) + protected MavenSession session; + @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true) private List repos; @@ -130,6 +134,7 @@ private void logTree(final Consumer log) throws MojoExecutionException { protected MavenArtifactResolver resolver() { return resolver == null ? resolver = workspaceProvider.createArtifactResolver(BootstrapMavenContext.config() + .setUserSettings(session.getRequest().getUserSettingsFile()) // The system needs to be initialized with the bootstrap model builder to properly interpolate system properties set on the command line // e.g. -Dquarkus.platform.version=xxx //.setRepositorySystem(repoSystem) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index b06e1b16173dc..34d04f14e98d9 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -1137,6 +1137,7 @@ private QuarkusDevModeLauncher newLauncher(Boolean debugPortOk, String bootstrap bootstrapProvider.close(); } else { final BootstrapMavenContextConfig mvnConfig = BootstrapMavenContext.config() + .setUserSettings(session.getRequest().getUserSettingsFile()) .setRemoteRepositories(repos) .setWorkspaceDiscovery(true) .setPreferPomsFromWorkspace(true) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/GoOfflineMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/GoOfflineMojo.java index 4502351c87d9b..d76dee2638e73 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/GoOfflineMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/GoOfflineMojo.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Set; +import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -43,6 +44,9 @@ public class GoOfflineMojo extends AbstractMojo { @Parameter(defaultValue = "${project}", readonly = true, required = true) MavenProject project; + @Parameter(defaultValue = "${session}", readonly = true) + MavenSession session; + @Component RepositorySystem repoSystem; @@ -118,6 +122,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { private MavenArtifactResolver getResolver() throws MojoExecutionException { return workspaceProvider.createArtifactResolver(BootstrapMavenContext.config() + .setUserSettings(session.getRequest().getUserSettingsFile()) .setCurrentProject(project.getBasedir().toString()) .setRemoteRepositoryManager(remoteRepositoryManager) .setRemoteRepositories(repos) 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 36bcdf8908ca3..2ae61b88271c5 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java @@ -180,6 +180,9 @@ private MavenArtifactResolver artifactResolver(QuarkusBootstrapMojo mojo, Launch if (mode == LaunchMode.DEVELOPMENT || mode == LaunchMode.TEST || isWorkspaceDiscovery(mojo)) { return 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 + .setUserSettings(mojo.mavenSession().getRequest().getUserSettingsFile()) .setCurrentProject(mojo.mavenProject().getFile().toString()) .setPreferPomsFromWorkspace(true) .setProjectModelProvider(getProjectMap(mojo.mavenSession())::get)); diff --git a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectMojoBase.java b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectMojoBase.java index 16043a4d5aa76..62a7e1437acf6 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectMojoBase.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectMojoBase.java @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.List; +import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Dependency; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -52,6 +53,9 @@ public abstract class QuarkusProjectMojoBase extends AbstractMojo { @Parameter(defaultValue = "${project}") protected MavenProject project; + @Parameter(defaultValue = "${session}", readonly = true) + MavenSession session; + @Component protected RepositorySystem repoSystem; diff --git a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectStateMojoBase.java b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectStateMojoBase.java index 9007e7315bb61..62a346128a09c 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectStateMojoBase.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusProjectStateMojoBase.java @@ -140,6 +140,7 @@ protected MavenArtifactResolver catalogArtifactResolver() throws MojoExecutionEx @Override protected MavenArtifactResolver initArtifactResolver() throws MojoExecutionException { return workspaceProvider.createArtifactResolver(BootstrapMavenContext.config() + .setUserSettings(session.getRequest().getUserSettingsFile()) .setRemoteRepositoryManager(remoteRepositoryManager) // The system needs to be initialized with the bootstrap model builder to properly interpolate system properties set on the command line // e.g. -Dquarkus.platform.version=xxx From 45dd9e7004786001cf1817e2a0d6df7c6f16f069 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Thu, 29 Jun 2023 10:43:53 +1000 Subject: [PATCH 22/22] Dev UI: Change log stream test to use less common logger Signed-off-by: Phillip Kruger (cherry picked from commit 5eb138341a94b946b4c51c60eb9fd63e62786117) --- .../test/java/io/quarkus/devui/LogstreamTest.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/devui/LogstreamTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/devui/LogstreamTest.java index 41c7649c9e44f..a8a734b5e5af2 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/devui/LogstreamTest.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/devui/LogstreamTest.java @@ -40,16 +40,24 @@ public void testGetLoggers() throws Exception { @Test public void testUpdateLoggers() throws Exception { // Get the level before - JsonNode getLoggerResponse = super.executeJsonRPCMethod("getLogger", Map.of("loggerName", "io.quarkus")); + JsonNode getLoggerResponse = super.executeJsonRPCMethod("getLogger", + Map.of("loggerName", "io.quarkus.devui.runtime.DevUIWebSocket")); Assertions.assertNotNull(getLoggerResponse); Assertions.assertEquals("INFO", getLoggerResponse.get("effectiveLevel").asText()); // Update the level JsonNode updateLogLevelResponse = super.executeJsonRPCMethod("updateLogLevel", - Map.of("loggerName", "io.quarkus", + Map.of("loggerName", "io.quarkus.devui.runtime.DevUIWebSocket", "levelValue", "DEBUG")); Assertions.assertNotNull(updateLogLevelResponse); Assertions.assertEquals("DEBUG", updateLogLevelResponse.get("effectiveLevel").asText()); + + // Restore the level + JsonNode restoreLogLevelResponse = super.executeJsonRPCMethod("updateLogLevel", + Map.of("loggerName", "io.quarkus.devui.runtime.DevUIWebSocket", + "levelValue", "INFO")); + Assertions.assertNotNull(restoreLogLevelResponse); + Assertions.assertEquals("INFO", restoreLogLevelResponse.get("effectiveLevel").asText()); } private boolean hasStartedLine(Iterator elements) {