Skip to content

Commit 055f883

Browse files
authored
Merge pull request #46273 from mkouba/issue-46270
Qute: add build item to exclude discovered templates
2 parents 4e5050f + cf5d0bc commit 055f883

File tree

6 files changed

+139
-23
lines changed

6 files changed

+139
-23
lines changed

extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ && isNotLocatedByCustomTemplateLocator(locatorPatternsBuildItem.getLocationPatte
427427
continue;
428428
}
429429
MethodInfo canonicalConstructor = recordClass.method(MethodDescriptor.INIT,
430-
recordClass.unsortedRecordComponents().stream().map(RecordComponentInfo::type)
430+
recordClass.recordComponentsInDeclarationOrder().stream().map(RecordComponentInfo::type)
431431
.toArray(Type[]::new));
432432

433433
AnnotationInstance checkedTemplateAnnotation = recordClass.declaredAnnotation(Names.CHECKED_TEMPLATE);
@@ -2185,6 +2185,7 @@ private String toKey(MethodInfo extensionMethod) {
21852185
@BuildStep
21862186
void collectTemplates(ApplicationArchivesBuildItem applicationArchives,
21872187
CurateOutcomeBuildItem curateOutcome,
2188+
List<TemplatePathExcludeBuildItem> templatePathExcludes,
21882189
BuildProducer<HotDeploymentWatchedFileBuildItem> watchedPaths,
21892190
BuildProducer<TemplatePathBuildItem> templatePaths,
21902191
BuildProducer<NativeImageResourceBuildItem> nativeImageResources,
@@ -2205,6 +2206,12 @@ public boolean test(String path) {
22052206
}
22062207
}).build());
22072208

2209+
List<Pattern> excludePatterns = new ArrayList<>(templatePathExcludes.size() + 1);
2210+
excludePatterns.add(config.templatePathExclude());
2211+
for (TemplatePathExcludeBuildItem exclude : templatePathExcludes) {
2212+
excludePatterns.add(Pattern.compile(exclude.getRegexPattern()));
2213+
}
2214+
22082215
final Set<ApplicationArchive> allApplicationArchives = applicationArchives.getAllApplicationArchives();
22092216
final Set<ArtifactKey> appArtifactKeys = new HashSet<>(allApplicationArchives.size());
22102217
for (var archive : allApplicationArchives) {
@@ -2215,20 +2222,21 @@ public boolean test(String path) {
22152222
// Skip extension archives that are also application archives
22162223
if (!appArtifactKeys.contains(artifact.getKey())) {
22172224
scanPathTree(artifact.getContentTree(), templateRoots, watchedPaths, templatePaths, nativeImageResources,
2218-
config);
2225+
config, excludePatterns);
22192226
}
22202227
}
22212228
for (ApplicationArchive archive : allApplicationArchives) {
22222229
archive.accept(
2223-
tree -> scanPathTree(tree, templateRoots, watchedPaths, templatePaths, nativeImageResources, config));
2230+
tree -> scanPathTree(tree, templateRoots, watchedPaths, templatePaths, nativeImageResources, config,
2231+
excludePatterns));
22242232
}
22252233
}
22262234

22272235
private void scanPathTree(PathTree pathTree, TemplateRootsBuildItem templateRoots,
22282236
BuildProducer<HotDeploymentWatchedFileBuildItem> watchedPaths,
22292237
BuildProducer<TemplatePathBuildItem> templatePaths,
22302238
BuildProducer<NativeImageResourceBuildItem> nativeImageResources,
2231-
QuteConfig config) {
2239+
QuteConfig config, List<Pattern> excludePatterns) {
22322240
for (String templateRoot : templateRoots) {
22332241
if (PathTreeUtils.containsCaseSensitivePath(pathTree, templateRoot)) {
22342242
pathTree.walkIfContains(templateRoot, visit -> {
@@ -2241,9 +2249,11 @@ private void scanPathTree(PathTree pathTree, TemplateRootsBuildItem templateRoot
22412249
// remove templateRoot + /
22422250
final String relativePath = visit.getRelativePath();
22432251
String templatePath = relativePath.substring(templateRoot.length() + 1);
2244-
if (config.templatePathExclude().matcher(templatePath).matches()) {
2245-
LOGGER.debugf("Template file excluded: %s", visit.getPath());
2246-
return;
2252+
for (Pattern p : excludePatterns) {
2253+
if (p.matcher(templatePath).matches()) {
2254+
LOGGER.debugf("Template file excluded: %s", visit.getPath());
2255+
return;
2256+
}
22472257
}
22482258
produceTemplateBuildItems(templatePaths, watchedPaths, nativeImageResources,
22492259
relativePath, templatePath, visit.getPath(), config);
@@ -2568,7 +2578,7 @@ void collecTemplateContents(BeanArchiveIndexBuildItem index, List<CheckedTemplat
25682578
@Record(value = STATIC_INIT)
25692579
void initialize(BuildProducer<SyntheticBeanBuildItem> syntheticBeans, QuteRecorder recorder,
25702580
List<TemplatePathBuildItem> templatePaths, Optional<TemplateVariantsBuildItem> templateVariants,
2571-
TemplateRootsBuildItem templateRoots) {
2581+
TemplateRootsBuildItem templateRoots, List<TemplatePathExcludeBuildItem> templatePathExcludes) {
25722582

25732583
List<String> templates = new ArrayList<>();
25742584
List<String> tags = new ArrayList<>();
@@ -2592,11 +2602,17 @@ void initialize(BuildProducer<SyntheticBeanBuildItem> syntheticBeans, QuteRecord
25922602
variants = Collections.emptyMap();
25932603
}
25942604

2605+
List<String> excludePatterns = new ArrayList<>(templatePathExcludes.size());
2606+
for (TemplatePathExcludeBuildItem exclude : templatePathExcludes) {
2607+
excludePatterns.add(exclude.getRegexPattern());
2608+
}
2609+
25952610
syntheticBeans.produce(SyntheticBeanBuildItem.configure(QuteContext.class)
25962611
.scope(BuiltinScope.SINGLETON.getInfo())
25972612
.supplier(recorder.createContext(templates,
25982613
tags, variants,
2599-
templateRoots.getPaths().stream().map(p -> p + "/").collect(Collectors.toSet()), templateContents))
2614+
templateRoots.getPaths().stream().map(p -> p + "/").collect(Collectors.toSet()), templateContents,
2615+
excludePatterns))
26002616
.done());
26012617
}
26022618

@@ -3529,8 +3545,7 @@ public static String getName(InjectionPointInfo injectionPoint) {
35293545
private static void produceTemplateBuildItems(BuildProducer<TemplatePathBuildItem> templatePaths,
35303546
BuildProducer<HotDeploymentWatchedFileBuildItem> watchedPaths,
35313547
BuildProducer<NativeImageResourceBuildItem> nativeImageResources, String resourcePath,
3532-
String templatePath,
3533-
Path originalPath, QuteConfig config) {
3548+
String templatePath, Path originalPath, QuteConfig config) {
35343549
if (templatePath.isEmpty()) {
35353550
return;
35363551
}
@@ -3544,9 +3559,10 @@ private static void produceTemplateBuildItems(BuildProducer<TemplatePathBuildIte
35443559
}
35453560
watchedPaths.produce(new HotDeploymentWatchedFileBuildItem(resourcePath, restartNeeded));
35463561
nativeImageResources.produce(new NativeImageResourceBuildItem(resourcePath));
3547-
templatePaths.produce(
3548-
new TemplatePathBuildItem(templatePath, originalPath,
3549-
readTemplateContent(originalPath, config.defaultCharset())));
3562+
templatePaths.produce(TemplatePathBuildItem.builder()
3563+
.path(templatePath)
3564+
.fullPath(originalPath)
3565+
.content(readTemplateContent(originalPath, config.defaultCharset())).build());
35503566
}
35513567

35523568
private static boolean isExcluded(TypeCheck check, Iterable<Predicate<TypeCheck>> excludes) {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.quarkus.qute.deployment;
2+
3+
import io.quarkus.builder.item.MultiBuildItem;
4+
5+
/**
6+
* This build item is used to exclude template files found in template roots. Excluded templates are
7+
* neither parsed nor validated during build and are not available at runtime.
8+
* <p>
9+
* The matched input is the file path relative from the root directory and the {@code /} is used as a path separator.
10+
*/
11+
public final class TemplatePathExcludeBuildItem extends MultiBuildItem {
12+
13+
private final String regexPattern;
14+
15+
public TemplatePathExcludeBuildItem(String regexPattern) {
16+
this.regexPattern = regexPattern;
17+
}
18+
19+
public String getRegexPattern() {
20+
return regexPattern;
21+
}
22+
23+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.quarkus.qute.deployment.exclude;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNull;
5+
6+
import java.util.function.Consumer;
7+
8+
import jakarta.inject.Inject;
9+
10+
import org.jboss.shrinkwrap.api.asset.StringAsset;
11+
import org.junit.jupiter.api.Test;
12+
import org.junit.jupiter.api.extension.RegisterExtension;
13+
14+
import io.quarkus.builder.BuildChainBuilder;
15+
import io.quarkus.builder.BuildContext;
16+
import io.quarkus.builder.BuildStep;
17+
import io.quarkus.qute.Engine;
18+
import io.quarkus.qute.deployment.TemplatePathExcludeBuildItem;
19+
import io.quarkus.test.QuarkusUnitTest;
20+
21+
public class TemplatePathExcludeBuildItemTest {
22+
23+
@RegisterExtension
24+
static final QuarkusUnitTest config = new QuarkusUnitTest()
25+
.withApplicationRoot(root -> root
26+
.addAsResource(new StringAsset("{@String name} Hi {name.nonexistent}!"), "templates/hi.txt")
27+
.addAsResource(new StringAsset("Hello {name}!"), "templates/hello.txt"))
28+
.addBuildChainCustomizer(buildCustomizer());
29+
30+
static Consumer<BuildChainBuilder> buildCustomizer() {
31+
return new Consumer<BuildChainBuilder>() {
32+
@Override
33+
public void accept(BuildChainBuilder builder) {
34+
builder.addBuildStep(new BuildStep() {
35+
@Override
36+
public void execute(BuildContext context) {
37+
context.produce(new TemplatePathExcludeBuildItem("hi.txt"));
38+
}
39+
}).produces(TemplatePathExcludeBuildItem.class)
40+
.build();
41+
42+
}
43+
};
44+
}
45+
46+
@Inject
47+
Engine engine;
48+
49+
@Test
50+
public void testTemplate() {
51+
assertNull(engine.getTemplate("hi"));
52+
assertEquals("Hello M!", engine.getTemplate("hello").data("name", "M").render());
53+
}
54+
55+
}

extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import io.quarkus.qute.EvalContext;
4141
import io.quarkus.qute.Expression;
4242
import io.quarkus.qute.HtmlEscaper;
43+
import io.quarkus.qute.ImmutableList;
4344
import io.quarkus.qute.JsonEscaper;
4445
import io.quarkus.qute.NamespaceResolver;
4546
import io.quarkus.qute.ParserHook;
@@ -82,7 +83,7 @@ public class EngineProducer {
8283
private final List<String> suffixes;
8384
private final Set<String> templateRoots;
8485
private final Map<String, String> templateContents;
85-
private final Pattern templatePathExclude;
86+
private final List<Pattern> templatePathExcludes;
8687
private final Locale defaultLocale;
8788
private final Charset defaultCharset;
8889
private final ArcContainer container;
@@ -97,11 +98,17 @@ public EngineProducer(QuteContext context, QuteConfig config, QuteRuntimeConfig
9798
this.templateRoots = context.getTemplateRoots();
9899
this.templateContents = Map.copyOf(context.getTemplateContents());
99100
this.tags = context.getTags();
100-
this.templatePathExclude = config.templatePathExclude();
101101
this.defaultLocale = locales.defaultLocale().orElse(Locale.getDefault());
102102
this.defaultCharset = config.defaultCharset();
103103
this.container = Arc.container();
104104

105+
ImmutableList.Builder<Pattern> excludesBuilder = ImmutableList.<Pattern> builder()
106+
.add(config.templatePathExclude());
107+
for (String p : context.getExcludePatterns()) {
108+
excludesBuilder.add(Pattern.compile(p));
109+
}
110+
this.templatePathExcludes = excludesBuilder.build();
111+
105112
LOGGER.debugf("Initializing Qute [templates: %s, tags: %s, resolvers: %s", context.getTemplatePaths(), tags,
106113
context.getResolverClasses());
107114

@@ -342,8 +349,17 @@ private TemplateGlobalProvider createGlobalProvider(String initializerClassName)
342349
}
343350
}
344351

352+
private boolean isExcluded(String path) {
353+
for (Pattern p : templatePathExcludes) {
354+
if (p.matcher(path).matches()) {
355+
return true;
356+
}
357+
}
358+
return false;
359+
}
360+
345361
private Optional<TemplateLocation> locate(String path) {
346-
if (templatePathExclude.matcher(path).matches()) {
362+
if (isExcluded(path)) {
347363
return Optional.empty();
348364
}
349365
// First try to locate file-based templates
@@ -356,7 +372,7 @@ private Optional<TemplateLocation> locate(String path) {
356372
// Try path with suffixes
357373
for (String suffix : suffixes) {
358374
String pathWithSuffix = path + "." + suffix;
359-
if (templatePathExclude.matcher(pathWithSuffix).matches()) {
375+
if (isExcluded(pathWithSuffix)) {
360376
continue;
361377
}
362378
templatePath = templateRoot + pathWithSuffix;
@@ -377,7 +393,7 @@ private Optional<TemplateLocation> locate(String path) {
377393
// Try path with suffixes
378394
for (String suffix : suffixes) {
379395
String pathWithSuffix = path + "." + suffix;
380-
if (templatePathExclude.matcher(pathWithSuffix).matches()) {
396+
if (isExcluded(pathWithSuffix)) {
381397
continue;
382398
}
383399
content = templateContents.get(pathWithSuffix);

extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,10 @@ public interface QuteConfig {
4949
Optional<List<String>> typeCheckExcludes();
5050

5151
/**
52-
* This regular expression is used to exclude template files from the {@code templates} directory. Excluded templates are
52+
* This regular expression is used to exclude template files found in template roots. Excluded templates are
5353
* neither parsed nor validated during build and are not available at runtime.
5454
* <p>
55-
* The matched input is the file path relative from the {@code templates} directory and the
56-
* {@code /} is used as a path separator.
55+
* The matched input is the file path relative from the root directory and the {@code /} is used as a path separator.
5756
* <p>
5857
* By default, the hidden files are excluded. The name of a hidden file starts with a dot.
5958
*/

extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
public class QuteRecorder {
1313

1414
public Supplier<Object> createContext(List<String> templatePaths, List<String> tags, Map<String, List<String>> variants,
15-
Set<String> templateRoots, Map<String, String> templateContents) {
15+
Set<String> templateRoots, Map<String, String> templateContents, List<String> excludePatterns) {
1616
return new Supplier<Object>() {
1717

1818
@Override
@@ -63,6 +63,11 @@ public Map<String, String> getTemplateContents() {
6363
return templateContents;
6464
}
6565

66+
@Override
67+
public List<String> getExcludePatterns() {
68+
return excludePatterns;
69+
}
70+
6671
@Override
6772
public void setGeneratedClasses(List<String> resolverClasses, List<String> templateGlobalProviderClasses) {
6873
this.resolverClasses = resolverClasses;
@@ -99,6 +104,8 @@ public interface QuteContext {
99104

100105
Map<String, String> getTemplateContents();
101106

107+
List<String> getExcludePatterns();
108+
102109
/**
103110
* The generated classes must be initialized after the template expressions are validated (later during the STATIC_INIT
104111
* bootstrap phase) in order to break the cycle in the build chain.

0 commit comments

Comments
 (0)