From 070aca5bad2107f3e1adb5c8fa9f7de5542c7efa Mon Sep 17 00:00:00 2001 From: Romain Grecourt Date: Mon, 7 Oct 2024 14:42:05 -0700 Subject: [PATCH] Add an "unpack" task to the stager-maven-plugin Download an unpack an archive from a URL. --- .../io/helidon/build/common/FileUtils.java | 13 ++- maven-plugins/stager-maven-plugin/README.md | 23 ++++- .../etc/spotbugs/exclude.xml | 8 +- .../src/it/projects/unpack-artifact/pom.xml | 4 +- .../src/it/projects/unpack/pom.xml | 73 ++++++++++++++ .../src/it/projects/unpack/postbuild.groovy | 29 ++++++ .../build/maven/stager/DownloadTask.java | 24 +++-- .../build/maven/stager/StagingContext.java | 13 ++- .../maven/stager/StagingContextImpl.java | 25 +++-- .../maven/stager/StagingElementFactory.java | 3 + .../build/maven/stager/UnpackTask.java | 96 +++++++++++++++++++ .../build/maven/stager/ConfigReaderTest.java | 62 ++++++------ .../build/maven/stager/ProjectsTestIT.java | 12 +++ .../src/test/resources/test-config.xml | 4 + 14 files changed, 338 insertions(+), 51 deletions(-) create mode 100644 maven-plugins/stager-maven-plugin/src/it/projects/unpack/pom.xml create mode 100644 maven-plugins/stager-maven-plugin/src/it/projects/unpack/postbuild.groovy create mode 100644 maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/UnpackTask.java diff --git a/common/common/src/main/java/io/helidon/build/common/FileUtils.java b/common/common/src/main/java/io/helidon/build/common/FileUtils.java index 07be4836a..2bb00edaa 100644 --- a/common/common/src/main/java/io/helidon/build/common/FileUtils.java +++ b/common/common/src/main/java/io/helidon/build/common/FileUtils.java @@ -103,7 +103,7 @@ public final class FileUtils { */ public static Path requiredDirectoryFromProperty(String systemPropertyName, boolean createIfRequired) { final String path = Requirements.requireNonNull(System.getProperty(systemPropertyName), - "Required system property %s not set", systemPropertyName); + "Required system property %s not set", systemPropertyName); return requiredDirectory(path, createIfRequired); } @@ -1006,7 +1006,16 @@ public static Path resourceAsPath(String path, Class clazz) throws IllegalArg * @return extension or {@code null} */ public static String fileExt(Path file) { - String filename = file.getFileName().toString(); + return fileExt(file.getFileName().toString()); + } + + /** + * Get the file extension for the given file name. + * + * @param filename file name + * @return extension or {@code null} + */ + public static String fileExt(String filename) { int index = filename.lastIndexOf("."); return index < 0 ? null : filename.substring(index + 1); } diff --git a/maven-plugins/stager-maven-plugin/README.md b/maven-plugins/stager-maven-plugin/README.md index 4fc0e9658..a3b8351b9 100644 --- a/maven-plugins/stager-maven-plugin/README.md +++ b/maven-plugins/stager-maven-plugin/README.md @@ -55,12 +55,33 @@ The above parameters are mapped to user properties of the form `stager.PROPERTY` + + + + + + + + + + + 1.2.3 + + + + + + + diff --git a/maven-plugins/stager-maven-plugin/etc/spotbugs/exclude.xml b/maven-plugins/stager-maven-plugin/etc/spotbugs/exclude.xml index dd45acef6..83865a962 100644 --- a/maven-plugins/stager-maven-plugin/etc/spotbugs/exclude.xml +++ b/maven-plugins/stager-maven-plugin/etc/spotbugs/exclude.xml @@ -1,7 +1,7 @@ + + + + diff --git a/maven-plugins/stager-maven-plugin/src/it/projects/unpack-artifact/pom.xml b/maven-plugins/stager-maven-plugin/src/it/projects/unpack-artifact/pom.xml index 94b33ff04..2ddf9d48c 100644 --- a/maven-plugins/stager-maven-plugin/src/it/projects/unpack-artifact/pom.xml +++ b/maven-plugins/stager-maven-plugin/src/it/projects/unpack-artifact/pom.xml @@ -1,6 +1,6 @@ + + + 4.0.0 + io.helidon.build-tools.stager.tests + unpack + @project.version@ + Test Stager Unpack task + pom + + + + + + io.helidon.build-tools + helidon-stager-maven-plugin + ${project.version} + + + + + + + + + 3.2.10 + 3.2.9 + + + + + + + + + + + + + + io.helidon.build-tools + helidon-stager-maven-plugin + ${project.version} + + + + stage + + + + + + + diff --git a/maven-plugins/stager-maven-plugin/src/it/projects/unpack/postbuild.groovy b/maven-plugins/stager-maven-plugin/src/it/projects/unpack/postbuild.groovy new file mode 100644 index 000000000..f0a7ec32c --- /dev/null +++ b/maven-plugins/stager-maven-plugin/src/it/projects/unpack/postbuild.groovy @@ -0,0 +1,29 @@ +/* + * Copyright (c) 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. + */ + +import io.helidon.build.common.test.utils.JUnitLauncher +import io.helidon.build.maven.stager.ProjectsTestIT + +//noinspection GroovyAssignabilityCheck,GrUnresolvedAccess +JUnitLauncher.builder() + .select(ProjectsTestIT.class, "testUnpack", String.class) + .parameter("basedir", basedir.getAbsolutePath()) + .reportsDir(basedir) + .outputFile(new File(basedir, "test.log")) + .suiteId("stager-unpack-it-test") + .suiteDisplayName("Stager Unpack Integration Test") + .build() + .launch() diff --git a/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/DownloadTask.java b/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/DownloadTask.java index b8af82614..fba218c2c 100644 --- a/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/DownloadTask.java +++ b/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/DownloadTask.java @@ -61,14 +61,24 @@ protected CompletableFuture execBody(StagingContext ctx, Path dir, Map vars) throws IOException { - download(ctx, dir, vars); - } - - private void download(StagingContext ctx, Path dir, Map vars) throws IOException { String path = resolveVar(target(), vars); Path file = dir.resolve(path).normalize(); ensureDirectory(file.getParent()); URL url = new URL(resolveVar(this.url, vars)); + download(ctx, url, file); + } + + /** + * Download a file. + * + * @param ctx staging context + * @param url url + * @param file target file + * @throws IOException if an IO error occurs + */ + static void download(StagingContext ctx, URL url, Path file) + throws IOException { + try (BufferedInputStream bis = new BufferedInputStream(open(url, ctx))) { try (OutputStream fos = Files.newOutputStream(file)) { int n; @@ -86,19 +96,19 @@ private void download(StagingContext ctx, Path dir, Map vars) th totalTime = (currentTime - startTime) / 1000; progressTime = currentTime; ctx.logInfo("Downloading %s to %s (%s at %s/s)", - url, path, measuredSize(totalSize), measuredSize(totalSize / totalTime)); + url, file, measuredSize(totalSize), measuredSize(totalSize / totalTime)); } } if (currentTime - progressTime >= 1000) { totalTime = (currentTime - startTime) / 1000; } ctx.logInfo("Downloaded %s to %s (%s at %s/s)", - url, path, measuredSize(totalSize), measuredSize(totalSize / totalTime)); + url, file, measuredSize(totalSize), measuredSize(totalSize / totalTime)); } } } - private InputStream open(URL url, StagingContext context) throws IOException { + private static InputStream open(URL url, StagingContext context) throws IOException { NetworkConnection.Builder builder = NetworkConnection.builder().url(url); int readTimeout = context.readTimeout(); if (readTimeout > 0) { diff --git a/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/StagingContext.java b/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/StagingContext.java index 79db61541..3a184ffd5 100644 --- a/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/StagingContext.java +++ b/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/StagingContext.java @@ -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. @@ -101,6 +101,17 @@ default Path createTempDirectory(String prefix) throws IOException { return Files.createTempDirectory(prefix); } + /** + * Create a temporary file. + * + * @param suffix suffix + * @return created file + * @throws IOException if an IO error occurs + */ + default Path createTempFile(String suffix) throws IOException { + return Files.createTempFile(null, suffix); + } + /** * Log an info message. * diff --git a/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/StagingContextImpl.java b/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/StagingContextImpl.java index fa04cf97e..689c91e0c 100644 --- a/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/StagingContextImpl.java +++ b/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/StagingContextImpl.java @@ -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. @@ -80,17 +80,17 @@ final class StagingContextImpl implements StagingContext { this.archiverManager = Objects.requireNonNull(archiverManager, "archiverManager is null"); this.executor = executor; this.readTimeout = Optional.ofNullable(propertyResolver.apply(StagingContext.READ_TIMEOUT_PROP)) - .map(Integer::parseInt) - .orElse(-1); + .map(Integer::parseInt) + .orElse(-1); this.connectTimeout = Optional.ofNullable(propertyResolver.apply(StagingContext.CONNECT_TIMEOUT_PROP)) - .map(Integer::parseInt) - .orElse(-1); + .map(Integer::parseInt) + .orElse(-1); this.taskTimeout = Optional.ofNullable(propertyResolver.apply(StagingContext.TASK_TIMEOUT_PROP)) - .map(Integer::parseInt) - .orElse(-1); + .map(Integer::parseInt) + .orElse(-1); this.maxRetries = Optional.ofNullable(propertyResolver.apply(StagingContext.MAX_RETRIES)) - .map(Integer::parseInt) - .orElse(-1); + .map(Integer::parseInt) + .orElse(-1); } @Override @@ -110,7 +110,7 @@ public void unpack(Path archive, Path target, String excludes, String includes) unArchiver.setSourceFile(archiveFile); unArchiver.setDestDirectory(target.toFile()); if (StringUtils.isNotEmpty(excludes) || StringUtils.isNotEmpty(includes)) { - IncludeExcludeFileSelector[] selectors = new IncludeExcludeFileSelector[]{ + IncludeExcludeFileSelector[] selectors = new IncludeExcludeFileSelector[] { new IncludeExcludeFileSelector() }; if (StringUtils.isNotEmpty(excludes)) { @@ -177,6 +177,11 @@ public Path createTempDirectory(String prefix) throws IOException { return Files.createTempDirectory(outputDir.toPath(), prefix); } + @Override + public Path createTempFile(String suffix) throws IOException { + return Files.createTempFile(outputDir.toPath(), null, suffix); + } + @Override public void logInfo(String msg, Object... args) { log.info(String.format(msg, args)); diff --git a/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/StagingElementFactory.java b/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/StagingElementFactory.java index 5cadd7058..4de35fbf5 100644 --- a/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/StagingElementFactory.java +++ b/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/StagingElementFactory.java @@ -41,6 +41,7 @@ class StagingElementFactory { FileTask.ELEMENT_NAME, SymlinkTask.ELEMENT_NAME, TemplateTask.ELEMENT_NAME, + UnpackTask.ELEMENT_NAME, UnpackArtifactTask.ELEMENT_NAME, ListFilesTask.ELEMENT_NAME); @@ -98,6 +99,8 @@ StagingAction createAction(String name, switch (name) { case StagingDirectory.ELEMENT_NAME: return new StagingDirectory(filterChildren(children, StagingAction.class), attrs); + case UnpackTask.ELEMENT_NAME: + return new UnpackTask(iterators.get(), attrs); case UnpackArtifactTask.ELEMENT_NAME: return new UnpackArtifactTask(iterators.get(), attrs); case CopyArtifactTask.ELEMENT_NAME: diff --git a/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/UnpackTask.java b/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/UnpackTask.java new file mode 100644 index 000000000..e3e8ea4b7 --- /dev/null +++ b/maven-plugins/stager-maven-plugin/src/main/java/io/helidon/build/maven/stager/UnpackTask.java @@ -0,0 +1,96 @@ +/* + * 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. + * 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.io.IOException; +import java.net.URL; +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import io.helidon.build.common.Strings; + +import static io.helidon.build.common.FileUtils.ensureDirectory; +import static io.helidon.build.common.FileUtils.fileExt; +import static io.helidon.build.maven.stager.DownloadTask.download; + +/** + * Download an unpack to a given target location. + */ +final class UnpackTask extends StagingTask { + + static final String ELEMENT_NAME = "unpack"; + + private final String ext; + private final String url; + private final String includes; + private final String excludes; + + UnpackTask(ActionIterators iterators, Map attrs) { + super(ELEMENT_NAME, null, iterators, attrs); + this.url = Strings.requireValid(attrs.get("url"), "url is required"); + this.ext = Strings.requireValid(Optional.ofNullable(attrs.get("ext")) + .orElseGet(() -> fileExt(url)), "ext is required"); + this.includes = attrs.get("includes"); + this.excludes = attrs.get("excludes"); + } + + /** + * Get the url. + * + * @return url, never {@code null} + */ + String url() { + return url; + } + + /** + * Get the excludes. + * + * @return excludes, may be {@code null} + */ + String excludes() { + return excludes; + } + + /** + * Get the includes. + * + * @return includes, may be {@code null} + */ + String includes() { + return includes; + } + + @Override + protected CompletableFuture execBody(StagingContext ctx, Path dir, Map vars) { + return execBodyWithTimeout(ctx, dir, vars); + } + + @Override + protected void doExecute(StagingContext ctx, Path dir, Map vars) throws IOException { + Path tempFile = ctx.createTempFile("." + ext); + URL url = new URL(resolveVar(this.url, vars)); + download(ctx, url, tempFile); + + String resolvedTarget = resolveVar(target(), vars); + Path targetDir = dir.resolve(resolvedTarget).normalize(); + ctx.logInfo("Unpacking %s to %s", tempFile, targetDir); + ensureDirectory(targetDir); + ctx.unpack(tempFile, targetDir, excludes, includes); + } +} diff --git a/maven-plugins/stager-maven-plugin/src/test/java/io/helidon/build/maven/stager/ConfigReaderTest.java b/maven-plugins/stager-maven-plugin/src/test/java/io/helidon/build/maven/stager/ConfigReaderTest.java index 0c60214c9..ad5197fc0 100644 --- a/maven-plugins/stager-maven-plugin/src/test/java/io/helidon/build/maven/stager/ConfigReaderTest.java +++ b/maven-plugins/stager-maven-plugin/src/test/java/io/helidon/build/maven/stager/ConfigReaderTest.java @@ -55,39 +55,39 @@ public void testConverter() throws Exception { StagingDirectory dir1 = (StagingDirectory) root.tasks().get(0); assertThat(dir1.target(), is("${project.build.directory}/site")); List dir1Tasks = dir1.tasks(); - assertThat(dir1Tasks.size(), is(6)); + assertThat(dir1Tasks.size(), is(7)); dir1Tasks.forEach(c -> assertThat(c, is(instanceOf(StagingTasks.class)))); List unpackArtifacts = ((StagingTasks) dir1Tasks.get(0)).tasks(); assertThat(unpackArtifacts.size(), is(2)); - UnpackArtifactTask unpack1 = (UnpackArtifactTask) unpackArtifacts.get(0); - assertThat(unpack1.gav().groupId(), is("io.helidon")); - assertThat(unpack1.gav().artifactId(), is("helidon-docs")); - assertThat(unpack1.gav().version(), is("{version}")); - assertThat(unpack1.excludes(), is("META-INF/**")); - assertThat(unpack1.includes(), is(nullValue())); - assertThat(unpack1.target(), is("docs/{version}")); - assertThat(unpack1.iterators().size(), is(1)); - assertThat(unpack1.iterators().get(0).next().get("version"), is("${docs.1.version}")); - assertThat(unpack1.iterators().get(0).next().get("version"), is("1.4.3")); - assertThat(unpack1.iterators().get(0).next().get("version"), is("1.4.2")); - assertThat(unpack1.iterators().get(0).next().get("version"), is("1.4.1")); - assertThat(unpack1.iterators().get(0).next().get("version"), is("1.4.0")); - assertThat(unpack1.iterators().get(0).hasNext(), is(false)); - - UnpackArtifactTask unpack2 = (UnpackArtifactTask) unpackArtifacts.get(1); - assertThat(unpack2.gav().groupId(), is("io.helidon")); - assertThat(unpack2.gav().artifactId(), is("helidon-project")); - assertThat(unpack2.gav().version(), is("{version}")); - assertThat(unpack2.gav().classifier(), is("site")); - assertThat(unpack2.excludes(), is("META-INF/**")); - assertThat(unpack2.includes(), is(nullValue())); - assertThat(unpack2.target(), is("docs/{version}")); - assertThat(unpack2.iterators().size(), is(1)); - assertThat(unpack2.iterators().get(0).next().get("version"), is("${docs.2.version}")); - assertThat(unpack2.iterators().get(0).hasNext(), is(false)); + UnpackArtifactTask unpackGAV1 = (UnpackArtifactTask) unpackArtifacts.get(0); + assertThat(unpackGAV1.gav().groupId(), is("io.helidon")); + assertThat(unpackGAV1.gav().artifactId(), is("helidon-docs")); + assertThat(unpackGAV1.gav().version(), is("{version}")); + assertThat(unpackGAV1.excludes(), is("META-INF/**")); + assertThat(unpackGAV1.includes(), is(nullValue())); + assertThat(unpackGAV1.target(), is("docs/{version}")); + assertThat(unpackGAV1.iterators().size(), is(1)); + assertThat(unpackGAV1.iterators().get(0).next().get("version"), is("${docs.1.version}")); + assertThat(unpackGAV1.iterators().get(0).next().get("version"), is("1.4.3")); + assertThat(unpackGAV1.iterators().get(0).next().get("version"), is("1.4.2")); + assertThat(unpackGAV1.iterators().get(0).next().get("version"), is("1.4.1")); + assertThat(unpackGAV1.iterators().get(0).next().get("version"), is("1.4.0")); + assertThat(unpackGAV1.iterators().get(0).hasNext(), is(false)); + + UnpackArtifactTask unpackGAV2 = (UnpackArtifactTask) unpackArtifacts.get(1); + assertThat(unpackGAV2.gav().groupId(), is("io.helidon")); + assertThat(unpackGAV2.gav().artifactId(), is("helidon-project")); + assertThat(unpackGAV2.gav().version(), is("{version}")); + assertThat(unpackGAV2.gav().classifier(), is("site")); + assertThat(unpackGAV2.excludes(), is("META-INF/**")); + assertThat(unpackGAV2.includes(), is(nullValue())); + assertThat(unpackGAV2.target(), is("docs/{version}")); + assertThat(unpackGAV2.iterators().size(), is(1)); + assertThat(unpackGAV2.iterators().get(0).next().get("version"), is("${docs.2.version}")); + assertThat(unpackGAV2.iterators().get(0).hasNext(), is(false)); List symlinks = ((StagingTasks) dir1Tasks.get(1)).tasks(); assertThat(symlinks.size(), is(4)); @@ -315,5 +315,13 @@ public void testConverter() throws Exception { assertThat(substitution.match(), is("^(?([^/]+/)*)index.html$")); assertThat(substitution.replace(), is("{path}")); assertThat(substitution.isRegex(), is(true)); + + List unpacks = ((StagingTasks) dir1Tasks.get(6)).tasks(); + assertThat(unpacks.size(), is(1)); + + UnpackTask unpack1 = (UnpackTask) unpacks.get(0); + assertThat(unpack1.url(), is("https://repo1.maven.org/maven2/io/helidon/helidon-project/3.2.10/helidon-project-3.2.10-site.jar")); + assertThat(unpack1.target(), is("3.2.10")); + assertThat(unpack1.tasks(), is(empty())); } } diff --git a/maven-plugins/stager-maven-plugin/src/test/java/io/helidon/build/maven/stager/ProjectsTestIT.java b/maven-plugins/stager-maven-plugin/src/test/java/io/helidon/build/maven/stager/ProjectsTestIT.java index b2bc81ca3..e3e4de6c5 100644 --- a/maven-plugins/stager-maven-plugin/src/test/java/io/helidon/build/maven/stager/ProjectsTestIT.java +++ b/maven-plugins/stager-maven-plugin/src/test/java/io/helidon/build/maven/stager/ProjectsTestIT.java @@ -211,6 +211,18 @@ void testUnpackArtifact(String basedir) { assertThat(docsDir.resolve("2.0.0-RC1"), fileExists()); } + @ParameterizedTest + @ConfigurationParameterSource("basedir") + void testUnpack(String basedir) { + Path stageDir = Path.of(basedir).resolve("target/stage"); + assertThat(stageDir, fileExists()); + + Path docsDir = stageDir.resolve("docs"); + assertThat(docsDir, fileExists()); + assertThat(docsDir.resolve("3.2.10"), fileExists()); + assertThat(docsDir.resolve("3.2.9"), fileExists()); + } + private String symlinkTarget(Path file) { try { assertThat(file + " is not a symbolic link", Files.isSymbolicLink(file), is(true)); diff --git a/maven-plugins/stager-maven-plugin/src/test/resources/test-config.xml b/maven-plugins/stager-maven-plugin/src/test/resources/test-config.xml index c9f1e1d4b..43eb140ae 100644 --- a/maven-plugins/stager-maven-plugin/src/test/resources/test-config.xml +++ b/maven-plugins/stager-maven-plugin/src/test/resources/test-config.xml @@ -192,5 +192,9 @@ + + +