Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add list-files task to stager-maven-plugin
Browse files Browse the repository at this point in the history
- Add a variant of FileUtils.walk that can follow symlinks (with FileVisitOption)
- Fix StagingElementFactory/ConfigReader to preserve order
romain-grecourt committed Jan 3, 2024
1 parent db58709 commit 3916dea
Showing 21 changed files with 701 additions and 302 deletions.
22 changes: 20 additions & 2 deletions common/common/src/main/java/io/helidon/build/common/FileUtils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2023 Oracle and/or its affiliates.
* Copyright (c) 2019, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
import java.nio.file.FileSystem;
import java.nio.file.FileSystemAlreadyExistsException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
@@ -271,11 +272,28 @@ public static List<Path> list(Path directory, int maxDepth) {
* @param directory The directory
* @param predicate predicate used to filter files and directories
* @return The normalized, absolute file paths.
* @see Files#walkFileTree(java.nio.file.Path, java.util.Set, int, java.nio.file.FileVisitor)
*/
public static List<Path> walk(Path directory, BiPredicate<Path, BasicFileAttributes> predicate) {
return walk(directory, Set.of(), predicate);
}

/**
* Walk the directory and return the files that match the given predicate.
* If a directory is filtered out by the predicate its subtree is skipped.
*
* @param directory The directory
* @param options options to configure the traversal
* @param predicate predicate used to filter files and directories
* @return The normalized, absolute file paths.
* @see Files#walkFileTree(java.nio.file.Path, java.util.Set, int, java.nio.file.FileVisitor)
*/
public static List<Path> walk(Path directory,
Set<FileVisitOption> options,
BiPredicate<Path, BasicFileAttributes> predicate) {
try {
List<Path> files = new ArrayList<>();
Files.walkFileTree(directory, new FileVisitor<>() {
Files.walkFileTree(directory, options, Integer.MAX_VALUE, new FileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
if (predicate.test(dir, attrs)) {
45 changes: 45 additions & 0 deletions common/common/src/main/java/io/helidon/build/common/Patterns.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.build.common;

import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
* {@link Pattern} utility.
*/
public final class Patterns {

private static final Pattern NAMED_GROUP_PATTERN = Pattern.compile("\\(\\?<([^!=].*?)>");

private Patterns() {
// cannot be instanciated
}

/**
* Get the group names defined in a regular expression.
*
* @param regex regular expression
* @return group names
*/
public static Set<String> groupNames(String regex) {
return NAMED_GROUP_PATTERN.matcher(regex)
.results()
.map(r -> r.group(1))
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2023 Oracle and/or its affiliates.
* Copyright (c) 2018, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,8 @@
import java.util.Objects;
import java.util.stream.Collectors;

import static io.helidon.build.common.Strings.normalizePath;

/**
* Utility class to parse and match path segments.
*/
@@ -62,7 +64,16 @@ public SourcePath(File dir, File file) {
* @param file the filed contained in the directory
*/
public SourcePath(Path dir, Path file) {
segments = parseSegments(getRelativePath(dir, file));
this(dir.relativize(file));
}

/**
* Create a new {@link SourcePath} instance for the given path.
*
* @param path the path to use
*/
public SourcePath(Path path) {
this(normalizePath(path));
}

/**
@@ -95,10 +106,6 @@ public SourcePath(List<String> paths) {
.toArray(String[]::new);
}

private static String getRelativePath(Path sourceDir, Path source) {
return Strings.normalizePath(sourceDir.relativize(source));
}

/**
* Parse a {@code '/'} separated path into segments. Collapses empty or {@code '.'} only segments.
*
3 changes: 2 additions & 1 deletion maven-plugins/stager-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2020, 2023 Oracle and/or its affiliates.
Copyright (c) 2020, 2024 Oracle and/or its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
<packaging>maven-plugin</packaging>

<properties>
<maven.compiler.release>17</maven.compiler.release>
<spotbugs.exclude>etc/spotbugs/exclude.xml</spotbugs.exclude>
</properties>

23 changes: 22 additions & 1 deletion maven-plugins/stager-maven-plugin/src/it/projects/file/pom.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!--
Copyright (c) 2020, 2022 Oracle and/or its affiliates.
Copyright (c) 2020, 2024 Oracle and/or its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -43,6 +43,27 @@
<files>
<file target="CNAME">${cname}</file>
<file target="cli-data/latest">${cli.data.latest.version}</file>
<file target="docs/4.0.2/index.html"/>
<file target="docs/4.0.2/apidocs/index.html"/>
<file target="docs/4.0.2/images/foo.svg"/>
</files>
<symlinks join="true">
<symlink source="docs/4.0.2" target="docs/v4"/>
</symlinks>
<files join="true">
<file target="sitemap.txt">
<list-files dir=".">
<includes>
<include>**/docs/**/*.html</include>
</includes>
<excludes>
<exclude>**/images/**</exclude>
</excludes>
<substitutions>
<substitution match="^(.*)/index.html$" replace="$1" />
</substitutions>
</list-files>
</file>
</files>
</directory>
</directories>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2022, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,10 @@
package io.helidon.build.maven.stager;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

@@ -28,7 +28,7 @@
*/
final class ConfigReader implements PlexusConfigNode.Visitor {

private final Map<PlexusConfigNode, Map<String, List<StagingElement>>> mappings;
private final Map<PlexusConfigNode, List<StagingElement>> mappings;
private final Deque<Scope> scopes;
private final StagingElementFactory factory;

@@ -46,9 +46,7 @@ final class ConfigReader implements PlexusConfigNode.Visitor {
*/
StagingTasks read(PlexusConfigNode node) {
node.visit(this);
StagingAction action = (StagingAction) mappings.get(node.parent())
.get(node.name())
.get(0);
StagingAction action = (StagingAction) mappings.get(node.parent()).iterator().next();
if (action instanceof StagingTasks) {
return (StagingTasks) action;
}
@@ -62,28 +60,26 @@ public void visitNode(PlexusConfigNode node) {

@Override
public void postVisitNode(PlexusConfigNode node) {
PlexusConfigNode nodeParent = node.parent();
String nodeName = node.name();
mappings.computeIfAbsent(node, n -> new LinkedHashMap<>());
mappings.computeIfAbsent(nodeParent, n -> new LinkedHashMap<>());
PlexusConfigNode parent = node.parent();
String name = node.name();
mappings.computeIfAbsent(node, n -> new ArrayList<>());
mappings.computeIfAbsent(parent, n -> new ArrayList<>());
Scope scope = scopes.peek();
if (scope == null) {
throw new IllegalStateException("Scope is not available");
}
StagingElement element = factory.create(
nodeName,
node.attributes(),
mappings.get(node),
node.value(),
scope);
if (element instanceof Variables) {
for (Variable variable : ((Variables) element)) {
StagingElement element = factory.create(name, node.attributes(), mappings.get(node), node.value(), scope);
if (element instanceof Variables variables) {
for (Variable variable : variables) {
scope.parent.variables.put(variable.name(), variable);
}
}
mappings.get(nodeParent)
.computeIfAbsent(nodeName, n -> new LinkedList<>())
.add(element);
List<StagingElement> siblings = mappings.computeIfAbsent(parent, n -> new ArrayList<>());
if (element instanceof StagingElements elements) {
siblings.addAll(elements.nested());
} else {
siblings.add(element);
}
scopes.pop();
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2022, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ final class DryRunStagingElementFactory extends StagingElementFactory {
@Override
StagingAction createAction(String name,
Map<String, String> attrs,
Map<String, List<StagingElement>> children,
List<StagingElement> children,
String text) {

StagingAction action = super.createAction(name, attrs, children, text);
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.build.maven.stager;

/**
* Exclude.
*/
record Exclude(String value) implements StagingElement {

static final String ELEMENT_NAME = "exclude";

@Override
public String elementName() {
return ELEMENT_NAME;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2022 Oracle and/or its affiliates.
* Copyright (c) 2020, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,9 +15,11 @@
*/
package io.helidon.build.maven.stager;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;

/**
@@ -30,8 +32,8 @@ final class FileTask extends StagingTask {
private final String content;
private final String source;

FileTask(ActionIterators iterators, Map<String, String> attrs, String content) {
super(ELEMENT_NAME, null, iterators, attrs);
FileTask(ActionIterators iterators, List<TextAction> nested, Map<String, String> attrs, String content) {
super(ELEMENT_NAME, nested, iterators, attrs);
this.content = content;
this.source = attrs.get("source");
}
@@ -50,10 +52,16 @@ String source() {
*
* @return content, may be {@code null}
*/
public String content() {
String content() {
return content;
}

@Override
@SuppressWarnings("unchecked")
List<TextAction> tasks() {
return (List<TextAction>) super.tasks();
}

@Override
protected void doExecute(StagingContext ctx, Path dir, Map<String, String> vars) throws IOException {
String resolvedTarget = resolveVar(target(), vars);
@@ -72,6 +80,12 @@ protected void doExecute(StagingContext ctx, Path dir, Map<String, String> vars)
Files.createFile(targetFile);
if (resolvedContent != null && !resolvedContent.isEmpty()) {
Files.writeString(targetFile, resolvedContent);
} else {
try (BufferedWriter writer = Files.newBufferedWriter(targetFile)) {
for (TextAction task : tasks()) {
writer.write(task.text());
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.build.maven.stager;

/**
* Include.
*/
record Include(String value) implements StagingElement {

static final String ELEMENT_NAME = "include";

@Override
public String elementName() {
return ELEMENT_NAME;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.build.maven.stager;

import java.nio.file.FileVisitOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;

import io.helidon.build.common.Lists;
import io.helidon.build.common.SourcePath;

import static io.helidon.build.common.FileUtils.walk;
import static io.helidon.build.common.Strings.normalizePath;

/**
* List files in a directory.
*/
final class ListFilesTask extends StagingTask implements TextAction {

private static final Set<FileVisitOption> FILE_VISIT_OPTIONS = Set.of(FileVisitOption.FOLLOW_LINKS);

static final String ELEMENT_NAME = "list-files";

private final List<String> includes;
private final List<String> excludes;
private final List<Substitution> substitutions;
private final List<BiFunction<String, Map<String, String>, String>> chain;
private final String dirName;
private final StringBuilder buf = new StringBuilder();

ListFilesTask(ActionIterators iterators,
List<Include> includes,
List<Exclude> excludes,
List<Substitution> substitutions,
Map<String, String> attrs) {

super(ELEMENT_NAME, null, iterators, attrs);
this.includes = Lists.map(includes, Include::value);
this.excludes = Lists.map(excludes, Exclude::value);
this.substitutions = substitutions;
this.chain = Lists.map(substitutions, Substitution::function);
this.dirName = attrs.getOrDefault("dir", ".");
}

@Override
public String text() {
return buf.toString();
}

List<String> includes() {
return includes;
}

List<String> excludes() {
return excludes;
}

List<Substitution> substitutions() {
return substitutions;
}

@Override
protected void doExecute(StagingContext ctx, Path dir, Map<String, String> vars) {
Path resolved = dir.resolve(dirName);
List<Path> files = walk(resolved, FILE_VISIT_OPTIONS, this::filter);
for (Path file : files) {
String entry = normalizePath(dir.relativize(file));
for (var function : chain) {
entry = function.apply(entry, vars);
}
buf.append(entry).append("\n");
}
}

private boolean filter(Path p, BasicFileAttributes attrs) {
return attrs.isDirectory() || new SourcePath(p).matches(includes, excludes);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2022 Oracle and/or its affiliates.
* Copyright (c) 2020, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,9 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Stream;

import io.helidon.build.maven.stager.ConfigReader.Scope;

@@ -32,18 +33,25 @@
*/
class StagingElementFactory {

private static final List<String> WRAPPED_ELEMENTS = List.of(
private static final Set<String> ACTION_ELEMENTS = Set.of(
StagingDirectory.ELEMENT_NAME,
ArchiveTask.ELEMENT_NAME,
DownloadTask.ELEMENT_NAME,
CopyArtifactTask.ELEMENT_NAME,
FileTask.ELEMENT_NAME,
SymlinkTask.ELEMENT_NAME,
TemplateTask.ELEMENT_NAME,
UnpackArtifactTask.ELEMENT_NAME);
UnpackArtifactTask.ELEMENT_NAME,
ListFilesTask.ELEMENT_NAME);

private static final Map<String, String> WRAPPER_ELEMENTS = WRAPPED_ELEMENTS
.stream()
private static final Set<String> SYNTHETIC_ELEMENTS = Set.of(
Include.ELEMENT_NAME,
Exclude.ELEMENT_NAME,
Substitution.ELEMENT_NAME);

private static final Map<String, String> WRAPPER_ELEMENTS = Stream.of(ACTION_ELEMENTS, SYNTHETIC_ELEMENTS)
.flatMap(Collection::stream)
.filter(n -> !n.endsWith("s"))
.collect(toMap(n -> n.endsWith("y") ? n.substring(0, n.length() - 1) + "ies" : n + "s", n -> n));

/**
@@ -58,101 +66,17 @@ class StagingElementFactory {
*/
StagingElement create(String name,
Map<String, String> attrs,
Map<String, List<StagingElement>> children,
List<StagingElement> children,
String text,
Scope scope) {

switch (name) {
case StagingDirectory.ELEMENT_NAME:
case UnpackArtifactTask.ELEMENT_NAME:
case CopyArtifactTask.ELEMENT_NAME:
case SymlinkTask.ELEMENT_NAME:
case DownloadTask.ELEMENT_NAME:
case ArchiveTask.ELEMENT_NAME:
case TemplateTask.ELEMENT_NAME:
case FileTask.ELEMENT_NAME:
return createAction(name, attrs, children, text);
case ActionIterators.ELEMENT_NAME:
return actionIterators(children, attrs);
case Variables.ELEMENT_NAME:
return variables(children, attrs);
case Variable.ELEMENT_NAME:
if (attrs.containsKey("ref")) {
return scope.resolve(attrs.get("ref"));
}
return variable(attrs.get("name"), children, attrs.get("value"));
case VariableValue.ELEMENT_NAME:
return variableValue(children, text);
default:
if (isWrapperElement(name)) {
return createAction(name, attrs, children, text);
}
throw new IllegalStateException("Unknown element: " + name);
}
}

/**
* Test if the given element name is a wrapper element.
*
* @param name element name
* @return {@code true} if the name is a wrapper element, {@code false} otherwise
*/
boolean isWrapperElement(String name) {
return WRAPPER_ELEMENTS.containsKey(name);
}

/**
* Create a new action iterators using the variables found in the given children.
*
* @param children child elements to process, should not be {@code null}
* @param attrs attributes
* @return ActionIterators, never {@code null}
*/
ActionIterators actionIterators(Map<String, List<StagingElement>> children, Map<String, String> attrs) {
List<ActionIterator> iterators = filterChildren(children, Variables.ELEMENT_NAME, Variables.class)
.stream()
.map(ActionIterator::new)
.collect(toList());
return new ActionIterators(iterators, attrs);
}

/**
* Create a new variables instance from the variable found in the given children.
*
* @param children child elements to process, should not be {@code null}
* @param attrs attributes
* @return Variables, never {@code null}
*/
Variables variables(Map<String, List<StagingElement>> children, Map<String, String> attrs) {
return new Variables(filterChildren(children, Variable.ELEMENT_NAME, Variable.class), attrs);
}

/**
* Create a new variable instance from the variable values found in the given children.
*
* @param children child elements to process, should not be {@code null}
* @return Variable, never {@code null}
*/
Variable variable(String name, Map<String, List<StagingElement>> children, String text) {
List<VariableValue> values = filterChildren(children, VariableValue.ELEMENT_NAME, VariableValue.class);
if (!values.isEmpty()) {
return new Variable(name, new VariableValue.ListValue(values));
}
return new Variable(name, new VariableValue.SimpleValue(text));
}

/**
* Create a new variable value instance from the given children.
*
* @param children child elements to process, should not be {@code null}
* @return VariableValue, never {@code null}
*/
VariableValue variableValue(Map<String, List<StagingElement>> children, String text) {
List<Variable> variables = filterChildren(children, Variable.ELEMENT_NAME, Variable.class);
if (!variables.isEmpty()) {
return new VariableValue.MapValue(variables);
}
return new VariableValue.SimpleValue(text);
return switch (name) {
case Variables.ELEMENT_NAME -> variables(attrs, children);
case Variable.ELEMENT_NAME -> variable(attrs, children, scope);
case VariableValue.ELEMENT_NAME -> variableValue(children, text);
case ActionIterators.ELEMENT_NAME -> actionIterators(children, attrs);
default -> createDefault(name, attrs, children, text);
};
}

/**
@@ -166,7 +90,7 @@ VariableValue variableValue(Map<String, List<StagingElement>> children, String t
*/
StagingAction createAction(String name,
Map<String, String> attrs,
Map<String, List<StagingElement>> children,
List<StagingElement> children,
String text) {

Supplier<ActionIterators> iterators = () -> firstChild(children, ActionIterators.class, () -> null);
@@ -187,52 +111,92 @@ StagingAction createAction(String name,
case TemplateTask.ELEMENT_NAME:
return new TemplateTask(iterators.get(), attrs, variables.get());
case FileTask.ELEMENT_NAME:
return new FileTask(iterators.get(), attrs, text);
return new FileTask(iterators.get(), filterChildren(children, TextAction.class), attrs, text);
case ListFilesTask.ELEMENT_NAME:
List<Include> includes = filterChildren(children, Include.class);
List<Exclude> excludes = filterChildren(children, Exclude.class);
List<Substitution> substitutions = filterChildren(children, Substitution.class);
return new ListFilesTask(iterators.get(), includes, excludes, substitutions, attrs);
default:
if (isWrapperElement(name)) {
if (WRAPPER_ELEMENTS.containsKey(name)) {
return new StagingTasks(name, filterChildren(children, StagingAction.class), attrs);
}
throw new IllegalStateException("Unknown action: " + name);
}
}

/**
* Filter the children with the given element name and type.
*
* @param mappings children mappings
* @param elementName map key to filter
* @param type type of the child to include
* @param <T> type parameter
* @return list of children, never {@code null}
*/
<T> List<T> filterChildren(Map<String, List<StagingElement>> mappings, String elementName, Class<T> type) {
return Optional.ofNullable(mappings.get(elementName)).stream()
.flatMap(Collection::stream)
.filter(type::isInstance)
.map(type::cast)
.collect(toList());
private StagingElement createDefault(String name,
Map<String, String> attrs,
List<StagingElement> children,
String text) {

if (SYNTHETIC_ELEMENTS.contains(name)) {
return synthetic(name, attrs, text);
} else if (ACTION_ELEMENTS.contains(name)) {
return createAction(name, attrs, children, text);
} else if (WRAPPER_ELEMENTS.containsKey(name)) {
String wrapped = WRAPPER_ELEMENTS.get(name);
if (SYNTHETIC_ELEMENTS.contains(wrapped)) {
return new StagingElements(name, filterChildren(children, StagingElement.class));
}
return createAction(name, attrs, children, text);
}
throw new IllegalStateException("Unknown element: " + name);
}

/**
* Filter the children with the given type.
*
* @param mappings children mappings
* @param type type of the child to include
* @param <T> type parameter
* @return list of children, never {@code null}
*/
<T> List<T> filterChildren(Map<String, List<StagingElement>> mappings, Class<T> type) {
return Optional.of(mappings.values())
.stream()
.flatMap(Collection::stream)
.flatMap(List::stream)
.filter(type::isInstance)
.map(type::cast)
.collect(toList());
private static Variables variables(Map<String, String> attrs, List<StagingElement> children) {
return new Variables(filterChildren(children, Variable.class), attrs);
}

private static Variable variable(Map<String, String> attrs, List<StagingElement> children, Scope scope) {
if (attrs.containsKey("ref")) {
return scope.resolve(attrs.get("ref"));
}
return variable(attrs.get("name"), children, attrs.get("value"));
}

private static Variable variable(String name, List<StagingElement> children, String text) {
List<VariableValue> values = filterChildren(children, VariableValue.class);
if (!values.isEmpty()) {
return new Variable(name, new VariableValue.ListValue(values));
}
return new Variable(name, new VariableValue.SimpleValue(text));
}

private static VariableValue variableValue(List<StagingElement> children, String text) {
List<Variable> variables = filterChildren(children, Variable.class);
if (!variables.isEmpty()) {
return new VariableValue.MapValue(variables);
}
return new VariableValue.SimpleValue(text);
}

private static ActionIterators actionIterators(List<StagingElement> children, Map<String, String> attrs) {
List<ActionIterator> iterators = filterChildren(children, Variables.class)
.stream()
.map(ActionIterator::new)
.collect(toList());
return new ActionIterators(iterators, attrs);
}

private static StagingElement synthetic(String name, Map<String, String> attrs, String text) {
return switch (name) {
case Include.ELEMENT_NAME -> new Include(text);
case Exclude.ELEMENT_NAME -> new Exclude(text);
case Substitution.ELEMENT_NAME -> new Substitution(attrs);
default -> throw new IllegalStateException("Unknown element: " + name);
};
}

private static <T> List<T> filterChildren(List<StagingElement> children, Class<T> type) {
return children.stream()
.filter(type::isInstance)
.map(type::cast)
.collect(toList());
}

<T> T firstChild(Map<String, List<StagingElement>> mappings, Class<T> type, Supplier<T> defaultValue) {
return filterChildren(mappings, type)
private static <T> T firstChild(List<StagingElement> children, Class<T> type, Supplier<T> defaultValue) {
return filterChildren(children, type)
.stream()
.findFirst()
.orElse(defaultValue.get());
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2022, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.build.maven.stager;

import java.util.List;

/**
* Container of {@link StagingElement}.
*/
record StagingElements(String elementName, List<StagingElement> nested) implements StagingElement {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2022 Oracle and/or its affiliates.
* Copyright (c) 2020, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,7 +46,7 @@
class StagingTask implements StagingAction {

private final String elementName;
private final List<StagingAction> nested;
private final List<? extends StagingAction> nested;
private final ActionIterators iterators;
private final Map<String, String> attrs;
private final String target;
@@ -56,7 +56,7 @@ class StagingTask implements StagingAction {
this(null, null, null, null);
}

StagingTask(String elementName, List<StagingAction> nested, ActionIterators iterators, Map<String, String> attrs) {
StagingTask(String elementName, List<? extends StagingAction> nested, ActionIterators iterators, Map<String, String> attrs) {
this.elementName = elementName != null ? elementName : "unknown";
this.nested = nested == null ? List.of() : nested;
this.iterators = iterators;
@@ -79,7 +79,7 @@ public String elementName() {
*
* @return tasks, never {@code null}
*/
List<StagingAction> tasks() {
List<? extends StagingAction> tasks() {
return nested;
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2022, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
/**
* Container of {@link StagingTask}.
*/
public class StagingTasks extends StagingTask {
final class StagingTasks extends StagingTask {

StagingTasks(String elementName, List<StagingAction> nested, Map<String, String> attrs) {
super(elementName, nested, null, attrs);
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.build.maven.stager;

import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;

import io.helidon.build.common.Patterns;
import io.helidon.build.common.Strings;

/**
* Substitution.
*/
final class Substitution implements StagingElement {

static final String ELEMENT_NAME = "substitution";

private final String match;
private final String replace;
private final boolean regex;

Substitution(Map<String, String> attrs) {
match = Strings.requireValid(attrs.get("match"), "match is required");
replace = Strings.requireValid(attrs.get("replace"), "replace is required");
regex = Boolean.parseBoolean(attrs.getOrDefault("regex", "true"));
}

String match() {
return match;
}

String replace() {
return replace;
}

boolean isRegex() {
return regex;
}

@Override
public String elementName() {
return ELEMENT_NAME;
}

BiFunction<String, Map<String, String>, String> function() {
return this::substitute;
}

String substitute(String str, Map<String, String> vars) {
String value = str;
String resolvedMatch = StagingTask.resolveVar(match, vars);
String resolvedReplace = StagingTask.resolveVar(replace, vars);
if (regex) {
// replace {group} with ${group}
Set<String> groups = Patterns.groupNames(resolvedMatch);
for (String group : groups) {
String token = "{" + group + "}";
resolvedReplace = resolvedReplace.replace(token, "$" + token);
}
value = value.replaceAll(resolvedMatch, resolvedReplace);
} else {
value = value.replace(resolvedMatch, resolvedReplace);
}
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.build.maven.stager;

/**
* A {@link StagingAction} that produces text.
*/
interface TextAction extends StagingAction {

/**
* Get the computed text.
*
* @return text
*/
String text();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2022 Oracle and/or its affiliates.
* Copyright (c) 2020, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,8 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.empty;

/**
* Tests {@link ConfigReader}.
@@ -52,12 +54,12 @@ public void testConverter() throws Exception {
assertThat(root.tasks().size(), is(1));
StagingDirectory dir1 = (StagingDirectory) root.tasks().get(0);
assertThat(dir1.target(), is("${project.build.directory}/site"));
List<StagingAction> dir1Tasks = dir1.tasks();
List<? extends StagingAction> dir1Tasks = dir1.tasks();
assertThat(dir1Tasks.size(), is(6));

dir1Tasks.forEach(c -> assertThat(c, is(instanceOf(StagingTasks.class))));

List<StagingAction> unpackArtifacts = ((StagingTasks) dir1Tasks.get(0)).tasks();
List<? extends StagingAction> unpackArtifacts = ((StagingTasks) dir1Tasks.get(0)).tasks();
assertThat(unpackArtifacts.size(), is(2));

UnpackArtifactTask unpack1 = (UnpackArtifactTask) unpackArtifacts.get(0);
@@ -87,7 +89,7 @@ public void testConverter() throws Exception {
assertThat(unpack2.iterators().get(0).next().get("version"), is("${docs.2.version}"));
assertThat(unpack2.iterators().get(0).hasNext(), is(false));

List<StagingAction> symlinks = ((StagingTasks) dir1Tasks.get(1)).tasks();
List<? extends StagingAction> symlinks = ((StagingTasks) dir1Tasks.get(1)).tasks();
assertThat(symlinks.size(), is(4));

SymlinkTask symlink1 = (SymlinkTask) symlinks.get(0);
@@ -106,7 +108,7 @@ public void testConverter() throws Exception {
assertThat(symlink4.source(), is("./${cli.latest.version}"));
assertThat(symlink4.target(), is("cli/latest"));

List<StagingAction> downloads = ((StagingTasks) dir1Tasks.get(2)).tasks();
List<? extends StagingAction> downloads = ((StagingTasks) dir1Tasks.get(2)).tasks();
assertThat(downloads.size(), is(2));

DownloadTask download1 = (DownloadTask) downloads.get(0);
@@ -137,15 +139,15 @@ public void testConverter() throws Exception {
assertThat(download2It4.get("version"), is("${cli.latest.version}"));
assertThat(download2.iterators().get(0).hasNext(), is(false));

List<StagingAction> archives = ((StagingTasks) dir1Tasks.get(3)).tasks();
List<? extends StagingAction> archives = ((StagingTasks) dir1Tasks.get(3)).tasks();
assertThat(archives.size(), is(1));

ArchiveTask archive = (ArchiveTask) archives.get(0);
List<StagingAction> archiveTasks = archive.tasks();
List<? extends StagingAction> archiveTasks = archive.tasks();
assertThat(archiveTasks.size(), is(2));
archiveTasks.forEach(c -> assertThat(c, is(instanceOf(StagingTasks.class))));

List<StagingAction> copyArtifacts = ((StagingTasks) archiveTasks.get(0)).tasks();
List<? extends StagingAction> copyArtifacts = ((StagingTasks) archiveTasks.get(0)).tasks();
assertThat(copyArtifacts.size(), is(3));

CopyArtifactTask archiveCopyArtifact1 = (CopyArtifactTask) copyArtifacts.get(0);
@@ -165,7 +167,7 @@ public void testConverter() throws Exception {
assertThat(archiveCopyArtifact3.gav().artifactId(), is("helidon-bare-mp"));
assertThat(archiveCopyArtifact3.gav().version(), is("${cli.data.latest.version}"));

List<StagingAction> templates = ((StagingTasks) archiveTasks.get(1)).tasks();
List<? extends StagingAction> templates = ((StagingTasks) archiveTasks.get(1)).tasks();
assertThat(templates.size(), is(1));

TemplateTask archiveTemplate1 = (TemplateTask) templates.get(0);
@@ -218,7 +220,7 @@ public void testConverter() throws Exception {
}
assertThat(index, is(3));

List<StagingAction> templates1 = ((StagingTasks) dir1Tasks.get(4)).tasks();
List<? extends StagingAction> templates1 = ((StagingTasks) dir1Tasks.get(4)).tasks();
assertThat(templates1.size(), is(3));

TemplateTask template1 = (TemplateTask) templates1.get(0);
@@ -281,17 +283,37 @@ public void testConverter() throws Exception {
assertThat(template3.templateVariables().get("og-description"), is(instanceOf(String.class)));
assertThat(template3.templateVariables().get("og-description"), is("Javadocs"));

List<StagingAction> files = ((StagingTasks) dir1Tasks.get(5)).tasks();
assertThat(files.size(), is(2));
List<? extends StagingAction> files = ((StagingTasks) dir1Tasks.get(5)).tasks();
assertThat(files.size(), is(3));

FileTask file1 = (FileTask) files.get(0);
assertThat(file1.source(), is(nullValue()));
assertThat(file1.target(), is("CNAME"));
assertThat(file1.content(), is("${cname}"));
assertThat(file1.tasks(), is(empty()));

FileTask file2 = (FileTask) files.get(1);
assertThat(file2.source(), is(nullValue()));
assertThat(file2.target(), is("cli-data/latest"));
assertThat(file2.content(), is("${cli.data.latest.version}"));
assertThat(file2.tasks(), is(empty()));

FileTask file3 = (FileTask) files.get(2);
assertThat(file3.source(), is(nullValue()));
assertThat(file3.target(), is("sitemap.txt"));
assertThat(file3.content(), is(nullValue()));
assertThat(file3.tasks().size(), is(1));

TextAction file3Task1 = file3.tasks().get(0);
assertThat(file3Task1, is(instanceOf(ListFilesTask.class)));
ListFilesTask listFiles = (ListFilesTask) file3Task1;
assertThat(listFiles.includes(), contains("**/foo/**", "**/bar/**"));
assertThat(listFiles.excludes(), contains("**/bob/**", "**/alice/**"));
assertThat(listFiles.substitutions().size(), is(1));

Substitution substitution = listFiles.substitutions().get(0);
assertThat(substitution.match(), is("^(?<path>([^/]+/)*)index.html$"));
assertThat(substitution.replace(), is("{path}"));
assertThat(substitution.isRegex(), is(true));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,13 +21,16 @@
import java.nio.file.Files;
import java.nio.file.Path;

import io.helidon.build.common.Strings;
import io.helidon.build.common.test.utils.ConfigurationParameterSource;

import org.junit.jupiter.params.ParameterizedTest;

import static io.helidon.build.common.FileUtils.newZipFileSystem;
import static io.helidon.build.common.Strings.normalizeNewLines;
import static io.helidon.build.common.test.utils.FileMatchers.fileExists;
import static java.nio.file.Files.readString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;

public class ProjectsTestIT {
@@ -36,140 +39,162 @@ public class ProjectsTestIT {
@ConfigurationParameterSource("basedir")
void testArchive(String basedir) throws IOException {
Path stageDir = Path.of(basedir).resolve("target/stage");
assertExist(stageDir);
assertThat(stageDir, fileExists());

Path archive = stageDir.resolve("cli-data/2.0.0-RC1/cli-data.zip");
assertExist(archive);
assertThat(archive, fileExists());

try (FileSystem fs = newZipFileSystem(archive)) {
Path file1 = fs.getPath("/").resolve("versions.json");
assertExist(file1);

assertEquals(Files.readString(file1), "{\n"
+ " \"versions\": [\n"
+ " \"3.0.0-SNAPSHOT\",\n"
+ " \"2.5.0\",\n"
+ " \"2.4.2\",\n"
+ " \"2.4.0\",\n"
+ " \"2.0.1\",\n"
+ " \"2.0.0\"\n"
+ " ],\n"
+ " \"latest\": \"3.0.0-SNAPSHOT\"\n"
+ "}\n");
Path file = fs.getPath("/").resolve("versions.json");
assertThat(file, fileExists());
assertThat(normalizeNewLines(readString(file)),
is("""
{
"versions": [
"3.0.0-SNAPSHOT",
"2.5.0",
"2.4.2",
"2.4.0",
"2.0.1",
"2.0.0"
],
"latest": "3.0.0-SNAPSHOT"
}
"""));
}
}

@ParameterizedTest
@ConfigurationParameterSource("basedir")
void testTemplate(String basedir) throws IOException {
Path stageDir = Path.of(basedir).resolve("target/stage");
assertExist(stageDir);
assertThat(stageDir, fileExists());

Path file1 = stageDir.resolve("versions1.json");
assertExist(file1);
assertEquals(Files.readString(file1), "{\n"
+ " \"versions\": [\n"
+ " \"3.0.0-SNAPSHOT\",\n"
+ " \"2.5.0\",\n"
+ " \"2.4.2\",\n"
+ " \"2.4.0\",\n"
+ " \"2.0.1\",\n"
+ " \"2.0.0\"\n"
+ " ],\n"
+ " \"preview-versions\": [\n"
+ " {\n"
+ " \"order\": 199,\n"
+ " \"version\": \"4.0.0-M1\"\n"
+ " },\n"
+ " {\n"
+ " \"order\": 200,\n"
+ " \"version\": \"4.0.0-ALPHA6\"\n"
+ " }\n"
+ " ],\n"
+ " \"latest\": \"3.0.0-SNAPSHOT\"\n"
+ "}\n");
assertThat(file1, fileExists());
assertThat(normalizeNewLines(readString(file1)),
is("""
{
"versions": [
"3.0.0-SNAPSHOT",
"2.5.0",
"2.4.2",
"2.4.0",
"2.0.1",
"2.0.0"
],
"preview-versions": [
{
"order": 199,
"version": "4.0.0-M1"
},
{
"order": 200,
"version": "4.0.0-ALPHA6"
}
],
"latest": "3.0.0-SNAPSHOT"
}
"""));

Path file2 = stageDir.resolve("versions2.json");
assertEquals(Files.readString(file2), "{\n"
+ " \"versions\": [\n"
+ " \"4.0.0-SNAPSHOT\",\n"
+ " \"3.0.0\"\n"
+ " ],\n"
+ " \"preview-versions\": [\n"
+ " ],\n"
+ " \"latest\": \"4.0.0-SNAPSHOT\"\n"
+ "}\n");
assertThat(file2, fileExists());
assertThat(normalizeNewLines(readString(file2)),
is("""
{
"versions": [
"4.0.0-SNAPSHOT",
"3.0.0"
],
"preview-versions": [
],
"latest": "4.0.0-SNAPSHOT"
}
"""));
}

@ParameterizedTest
@ConfigurationParameterSource("basedir")
void testCopyArtifact(String basedir) {
Path stageDir = Path.of(basedir).resolve("target/stage");
assertExist(stageDir);
assertExist(stageDir.resolve("helidon-bare-mp-2.0.0-RC1.jar"));
assertExist(stageDir.resolve("helidon-bare-se-2.0.0-RC1.jar"));
assertExist(stageDir.resolve("archetype-catalog.xml"));
assertThat(stageDir, fileExists());
assertThat(stageDir.resolve("helidon-bare-mp-2.0.0-RC1.jar"), fileExists());
assertThat(stageDir.resolve("helidon-bare-se-2.0.0-RC1.jar"), fileExists());
assertThat(stageDir.resolve("archetype-catalog.xml"), fileExists());
}

@ParameterizedTest
@ConfigurationParameterSource("basedir")
void testDownload(String basedir) {
Path stageDir = Path.of(basedir).resolve("target/stage");
assertExist(stageDir);
assertExist(stageDir.resolve("cli/2.0.0-RC1/darwin/helidon"));
assertExist(stageDir.resolve("cli/2.0.0-M4/linux/helidon"));
assertExist(stageDir.resolve("cli/2.0.0-M4/darwin/helidon"));
assertExist(stageDir.resolve("cli/2.0.0-RC1/linux/helidon"));
assertThat(stageDir, fileExists());
assertThat(stageDir.resolve("cli/2.0.0-RC1/darwin/helidon"), fileExists());
assertThat(stageDir.resolve("cli/2.0.0-M4/linux/helidon"), fileExists());
assertThat(stageDir.resolve("cli/2.0.0-M4/darwin/helidon"), fileExists());
assertThat(stageDir.resolve("cli/2.0.0-RC1/linux/helidon"), fileExists());
}

@ParameterizedTest
@ConfigurationParameterSource("basedir")
void testFile(String basedir) throws IOException {
Path stageDir = Path.of(basedir).resolve("target/stage");
assertExist(stageDir);
assertThat(stageDir, fileExists());

Path file1 = stageDir.resolve("cli-data/latest");
assertExist(file1);
assertEquals("2.0.0-RC1", Files.readString(file1));
assertThat(file1, fileExists());
assertThat(readString(file1), is("2.0.0-RC1"));

Path file2 = stageDir.resolve("CNAME");
assertExist(file2);
assertEquals("helidon.io", Files.readString(file2));
assertThat(file2, fileExists());
assertThat(readString(file2), is("helidon.io"));

assertThat(stageDir.resolve("docs/v4/index.html"), fileExists());
assertThat(stageDir.resolve("docs/v4/apidocs/index.html"), fileExists());
assertThat(stageDir.resolve("docs/v4/images/foo.svg"), fileExists());

Path file3 = stageDir.resolve("sitemap.txt");
assertThat(file3, fileExists());
assertThat(Files.readAllLines(file3), containsInAnyOrder(
"docs/v4",
"docs/v4/apidocs",
"docs/4.0.2",
"docs/4.0.2/apidocs"
));
}

@ParameterizedTest
@ConfigurationParameterSource("basedir")
void testSymlink(String basedir) {
Path stageDir = Path.of(basedir).resolve("target/stage");
assertExist(stageDir);
assertThat(stageDir, fileExists());

Path file1 = stageDir.resolve("cli/latest");
assertEquals(symlinkTarget(file1), "2.0.0-RC1");
assertThat(symlinkTarget(file1), is("2.0.0-RC1"));

Path file2 = stageDir.resolve("docs/latest");
assertEquals(symlinkTarget(file2), "1.4.4");
assertThat(symlinkTarget(file2), is("1.4.4"));

Path file3 = stageDir.resolve("docs/v1");
assertEquals(symlinkTarget(file3), "1.4.4");
assertThat(symlinkTarget(file3), is("1.4.4"));

Path file4 = stageDir.resolve("docs/v2");
assertEquals(symlinkTarget(file4), "2.0.0-RC1");
assertThat(symlinkTarget(file4), is("2.0.0-RC1"));
}

@ParameterizedTest
@ConfigurationParameterSource("basedir")
void testUnpackArtifact(String basedir) {
Path stageDir = Path.of(basedir).resolve("target/stage");
assertExist(stageDir);
assertThat(stageDir, fileExists());

Path docsDir = stageDir.resolve("docs");
assertExist(docsDir);
assertExist(docsDir.resolve("1.4.0"));
assertExist(docsDir.resolve("1.4.1"));
assertExist(docsDir.resolve("1.4.2"));
assertExist(docsDir.resolve("1.4.3"));
assertExist(docsDir.resolve("1.4.4"));
assertExist(docsDir.resolve("2.0.0-RC1"));
assertThat(docsDir, fileExists());
assertThat(docsDir.resolve("1.4.0"), fileExists());
assertThat(docsDir.resolve("1.4.1"), fileExists());
assertThat(docsDir.resolve("1.4.2"), fileExists());
assertThat(docsDir.resolve("1.4.3"), fileExists());
assertThat(docsDir.resolve("1.4.4"), fileExists());
assertThat(docsDir.resolve("2.0.0-RC1"), fileExists());
}

private String symlinkTarget(Path file) {
@@ -180,12 +205,4 @@ private String symlinkTarget(Path file) {
throw new UncheckedIOException(e);
}
}

private void assertExist(Path path) {
assertThat(path + " does not exist", Files.exists(path), is(true));
}

private void assertEquals(String actual, String expected) {
assertThat(Strings.normalizeNewLines(actual), is(expected));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2022 Oracle and/or its affiliates.
* Copyright (c) 2020, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -153,20 +153,7 @@ protected void doExecute(StagingContext ctx, Path dir, Map<String, String> vars)
throw new IllegalStateException();
}
};
StagingTask subTask2 = new StagingTask() {

@Override
public CompletionStage<Void> execute(StagingContext ctx, Path dir, Map<String, String> vars) {
return super.execute(ctx, dir, vars).exceptionally(exceptionally);
}

@Override
protected void doExecute(StagingContext ctx, Path dir, Map<String, String> vars) {
throw new IllegalStateException();
}
};

StagingTask task = new StagingTask(null, List.of(subTask1, subTask2), null, null);
StagingTask task = withFailedSubTask(exceptionally, subTask1);
StagingContext context = new TestContextImpl(Executors.newCachedThreadPool());
try {
task.execute(context, null, Map.of()).toCompletableFuture().get();
@@ -248,6 +235,21 @@ protected void doExecute(StagingContext ctx, Path dir, Map<String, String> vars)
assertThat(list, is(List.of(1, 2)));
}

private static StagingTask withFailedSubTask(Function<Throwable, Void> exceptionally, StagingTask subTask1) {
return new StagingTask(null, List.of(subTask1, new StagingTask() {

@Override
public CompletionStage<Void> execute(StagingContext ctx, Path dir, Map<String, String> vars) {
return super.execute(ctx, dir, vars).exceptionally(exceptionally);
}

@Override
protected void doExecute(StagingContext ctx, Path dir, Map<String, String> vars) {
throw new IllegalStateException();
}
}), null, null);
}

static void sleep(int seconds) {
try {
Thread.sleep(seconds * 1000L);
@@ -256,17 +258,6 @@ static void sleep(int seconds) {
}
}

static class TestContextImpl implements StagingContext {

final Executor executor;

TestContextImpl(Executor executor) {
this.executor = executor;
}

@Override
public Executor executor() {
return executor;
}
record TestContextImpl(Executor executor) implements StagingContext {
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2020, 2022 Oracle and/or its affiliates.
Copyright (c) 2020, 2024 Oracle and/or its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -174,6 +174,23 @@
<files>
<file target="CNAME">${cname}</file>
<file target="cli-data/latest">${cli.data.latest.version}</file>
<file target="sitemap.txt">
<list-files dir="docs">
<includes>
<include>**/foo/**</include>
<include>**/bar/**</include>
</includes>
<excludes>
<exclude>**/bob/**</exclude>
<exclude>**/alice/**</exclude>
</excludes>
<substitutions>
<substitution
match="^(?&lt;path&gt;([^/]+/)*)index.html$"
replace="{path}" />
</substitutions>
</list-files>
</file>
</files>
</directory>
</directories>

0 comments on commit 3916dea

Please sign in to comment.