From bdc9dc4c4c73a5254c1a8402a2bd7e933965df2a Mon Sep 17 00:00:00 2001 From: Shannon Pamperl Date: Mon, 3 Jul 2023 09:37:44 -0500 Subject: [PATCH] Add `UpdateMavenWrapper` recipe including checksum verification (#3392) * Add `UpdateMavenWrapper` recipe including checksum verification Fixes gh-1565 and gh-2996 * Add missing license headers * Ensure cmd files are using crlf line endings --- .gitattributes | 1 + .../main/java/org/openrewrite/Checksum.java | 15 +- .../java/org/openrewrite/remote/Remote.java | 11 +- .../org/openrewrite/remote/RemoteArchive.java | 4 + .../org/openrewrite/remote/RemoteFile.java | 4 + rewrite-maven/build.gradle.kts | 2 +- .../openrewrite/maven/UpdateMavenWrapper.java | 482 ++++++++++++++++++ .../maven/utilities/MavenWrapper.java | 216 ++++++++ .../maven/UpdateMavenWrapperTest.java | 418 +++++++++++++++ rewrite-maven/src/test/resources/mvnw | 287 +++++++++++ rewrite-maven/src/test/resources/mvnw.cmd | 187 +++++++ 11 files changed, 1619 insertions(+), 8 deletions(-) create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenWrapper.java create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java create mode 100644 rewrite-maven/src/test/resources/mvnw create mode 100644 rewrite-maven/src/test/resources/mvnw.cmd diff --git a/.gitattributes b/.gitattributes index d21dc057c39..a7e78915851 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,6 +7,7 @@ # These are explicitly windows files and should use crlf *.bat text eol=crlf +*.cmd text eol=crlf # These files are text and should be normalized (Convert crlf => lf) *.bash text eol=lf diff --git a/rewrite-core/src/main/java/org/openrewrite/Checksum.java b/rewrite-core/src/main/java/org/openrewrite/Checksum.java index 5a3c4a87270..918beb9c7cf 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Checksum.java +++ b/rewrite-core/src/main/java/org/openrewrite/Checksum.java @@ -18,6 +18,7 @@ import lombok.Value; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.ipc.http.HttpSender; +import org.openrewrite.remote.Remote; import java.io.IOException; import java.io.InputStream; @@ -99,14 +100,20 @@ public static SourceFile checksum(SourceFile sourceFile, @Nullable String algori try { MessageDigest md = MessageDigest.getInstance(algorithm); - try (InputStream is = Files.newInputStream(sourceFile.getSourcePath()); - DigestInputStream dis = new DigestInputStream(is, md)) { + InputStream is; + if (sourceFile instanceof Remote) { + is = ((Remote) sourceFile).getInputStream(new InMemoryExecutionContext()); + } else { + is = Files.newInputStream(sourceFile.getSourcePath()); + } + + try (DigestInputStream dis = new DigestInputStream(is, md)) { //noinspection StatementWithEmptyBody while (dis.read() != -1) { - // read decorated stream to EOF + // read stream to EOF } + return sourceFile.withChecksum(new Checksum(algorithm, md.digest())); } - return sourceFile.withChecksum(new Checksum(algorithm, md.digest())); } catch (NoSuchAlgorithmException | IOException e) { throw new IllegalArgumentException(e); } diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/Remote.java b/rewrite-core/src/main/java/org/openrewrite/remote/Remote.java index 3399a7bd87a..daed280455d 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/Remote.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/Remote.java @@ -170,19 +170,24 @@ public Builder fileAttributes(FileAttributes fileAttributes) { return this; } + public Builder checksum(Checksum checksum) { + this.checksum = checksum; + return this; + } + public RemoteFile build() { - return new RemoteFile(id, sourcePath, markers, uri, charset, charsetBomMarked, fileAttributes, description); + return new RemoteFile(id, sourcePath, markers, uri, charset, charsetBomMarked, fileAttributes, description, checksum); } public RemoteArchive build(Path path) { return new RemoteArchive(id, sourcePath, markers, uri, charset, charsetBomMarked, fileAttributes, description, Arrays.asList(path.toString().replace("/", "\\/").replace(".", "\\.") - .split("!"))); + .split("!")), checksum); } public RemoteArchive build(String... paths) { return new RemoteArchive(id, sourcePath, markers, uri, charset, charsetBomMarked, fileAttributes, description, - Arrays.asList(paths)); + Arrays.asList(paths), checksum); } } } diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java index 56ca127d21e..bb736eba396 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java @@ -19,6 +19,7 @@ import lombok.Value; import lombok.With; import org.intellij.lang.annotations.Language; +import org.openrewrite.Checksum; import org.openrewrite.ExecutionContext; import org.openrewrite.FileAttributes; import org.openrewrite.HttpSenderExecutionContextView; @@ -81,6 +82,9 @@ public class RemoteArchive implements Remote { */ List paths; + @Nullable + Checksum checksum; + @Override public InputStream getInputStream(ExecutionContext ctx) { HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java index edd2798e790..b4a0e91b910 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java @@ -19,6 +19,7 @@ import lombok.Value; import lombok.With; import org.intellij.lang.annotations.Language; +import org.openrewrite.Checksum; import org.openrewrite.ExecutionContext; import org.openrewrite.FileAttributes; import org.openrewrite.HttpSenderExecutionContextView; @@ -57,6 +58,9 @@ public class RemoteFile implements Remote { @Language("markdown") String description; + @Nullable + Checksum checksum; + @Override public InputStream getInputStream(ExecutionContext ctx) { HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); diff --git a/rewrite-maven/build.gradle.kts b/rewrite-maven/build.gradle.kts index 237ce066086..2a9c6f06417 100755 --- a/rewrite-maven/build.gradle.kts +++ b/rewrite-maven/build.gradle.kts @@ -31,7 +31,7 @@ dependencies { compileOnly("org.rocksdb:rocksdbjni:latest.release") compileOnly(project(":rewrite-yaml")) - compileOnly(project(":rewrite-properties")) + implementation(project(":rewrite-properties")) implementation("io.micrometer:micrometer-core:1.9.+") diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenWrapper.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenWrapper.java new file mode 100644 index 00000000000..f98856588f8 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenWrapper.java @@ -0,0 +1,482 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://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 org.openrewrite.maven; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import lombok.experimental.NonFinal; +import org.openrewrite.*; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.marker.BuildTool; +import org.openrewrite.marker.Markers; +import org.openrewrite.maven.utilities.MavenWrapper; +import org.openrewrite.properties.PropertiesIsoVisitor; +import org.openrewrite.properties.PropertiesParser; +import org.openrewrite.properties.PropertiesVisitor; +import org.openrewrite.properties.search.FindProperties; +import org.openrewrite.properties.tree.Properties; +import org.openrewrite.quark.Quark; +import org.openrewrite.remote.Remote; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; +import org.openrewrite.text.PlainText; + +import java.time.ZonedDateTime; +import java.util.*; + +import static java.util.Objects.requireNonNull; +import static org.openrewrite.PathUtils.equalIgnoringSeparators; +import static org.openrewrite.internal.StringUtils.isBlank; +import static org.openrewrite.maven.utilities.MavenWrapper.*; + +/** + * This recipe expects for the specified repository to be a Maven layout with `maven-metadata.xml` files containing all + * the following REQUIRED publications: + * + * org.apache.maven.wrapper:maven-wrapper:{wrapperVersion} + * org.apache.maven.wrapper:maven-wrapper-distribution:{wrapperVersion} + * org.apache.maven:apache-maven:{distributionVersion} + */ +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +@EqualsAndHashCode(callSuper = true) +public class UpdateMavenWrapper extends ScanningRecipe { + private static final String DISTRIBUTION_URL_KEY = "distributionUrl"; + private static final String DISTRIBUTION_SHA_256_SUM_KEY = "distributionSha256Sum"; + private static final String WRAPPER_URL_KEY = "wrapperUrl"; + private static final String WRAPPER_SHA_256_SUM_KEY = "wrapperSha256Sum"; + + @Getter + @Option(displayName = "New wrapper version", + description = "An exact version number or node-style semver selector used to select the wrapper version number.", + example = "3.x", + required = false) + @Nullable + final String wrapperVersion; + + @Getter + @Option(displayName = "Wrapper Distribution type", + description = "The distribution of the Maven wrapper to use.\n\n" + + "* \"bin\" uses a `maven-wrapper.jar` compiled binary.\n" + + "* \"only-script\" uses a lite version of `mvnw`/`mvnw.cmd` using wget/curl or powershell. (required wrapper 3.2.0 or newer)\n" + + "* \"script\" downloads `maven-wrapper.jar` or `MavenWrapperDownloader.java` to then download a full distribution.\n" + + "* \"source\" uses `MavenWrapperDownloader.java` source file.\n\n" + + "Defaults to \"bin\".", + valid = {"bin", "only-script", "script", "source"}, + required = false) + @Nullable + final String wrapperDistribution; + + @Getter + @Option(displayName = "New distribution version", + description = "An exact version number or node-style semver selector used to select the Maven version number.", + example = "3.x", + required = false) + @Nullable + final String distributionVersion; + + @Getter + @Option(displayName = "Repository URL", + description = "The URL of the repository to download the Maven wrapper and distribution from. Supports repositories " + + "with a Maven layout. Defaults to `https://repo.maven.apache.org/maven2`.", + example = "https://repo.maven.apache.org/maven2", + required = false) + @Nullable + final String repositoryUrl; + + @Getter + @Option(displayName = "Add if missing", + description = "Add a Maven wrapper, if it's missing. Defaults to `true`.", + required = false) + @Nullable + final Boolean addIfMissing; + + @Override + public String getDisplayName() { + return "Update Maven wrapper"; + } + + @Override + public String getDescription() { + return "Update the version of Maven used in an existing Maven wrapper."; + } + + @Override + public Validated validate() { + Validated validated = super.validate(); + if (wrapperVersion != null) { + validated = validated.and(Semver.validate(wrapperVersion, null)); + } + if (distributionVersion != null) { + validated = validated.and(Semver.validate(distributionVersion, null)); + } + return validated; + } + + @NonFinal + @Nullable + transient MavenWrapper mavenWrapper; + + private MavenWrapper getMavenWrapper(ExecutionContext ctx) { + if (mavenWrapper == null) { + mavenWrapper = MavenWrapper.create(wrapperVersion, wrapperDistribution, distributionVersion, repositoryUrl, ctx); + } + return mavenWrapper; + } + + static class MavenWrapperState { + boolean needsWrapperUpdate = false; + @Nullable BuildTool updatedMarker; + boolean addMavenWrapperProperties = true; + boolean addMavenWrapperDownloader = true; + boolean addMavenWrapperJar = true; + boolean addMavenShellScript = true; + boolean addMavenBatchScript = true; + } + + @Override + public MavenWrapperState getInitialValue(ExecutionContext ctx) { + return new MavenWrapperState(); + } + + @Override + public TreeVisitor getScanner(MavenWrapperState acc) { + return Preconditions.or( + new PropertiesVisitor() { + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + if (!super.isAcceptable(sourceFile, ctx)) { + return false; + } + + if (equalIgnoringSeparators(sourceFile.getSourcePath(), WRAPPER_PROPERTIES_LOCATION)) { + acc.addMavenWrapperProperties = false; + } else if (!PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_PROPERTIES_LOCATION_RELATIVE_PATH)) { + return false; + } + + Optional maybeBuildTool = sourceFile.getMarkers().findFirst(BuildTool.class); + if (!maybeBuildTool.isPresent()) { + return false; + } + BuildTool buildTool = maybeBuildTool.get(); + if (buildTool.getType() != BuildTool.Type.Maven) { + return false; + } + + MavenWrapper mavenWrapper = getMavenWrapper(ctx); + + VersionComparator versionComparator = requireNonNull(Semver.validate(isBlank(distributionVersion) ? "latest.release" : distributionVersion, null).getValue()); + int compare = versionComparator.compare(null, buildTool.getVersion(), mavenWrapper.getDistributionVersion()); + // maybe we want to update the distribution url + if (compare < 0) { + acc.needsWrapperUpdate = true; + acc.updatedMarker = buildTool.withVersion(mavenWrapper.getDistributionVersion()); + return true; + } else return compare == 0; + } + + @Override + public Properties visitFile(Properties.File file, ExecutionContext executionContext) { + Properties p = super.visitFile(file, executionContext); + if (FindProperties.find(p, DISTRIBUTION_SHA_256_SUM_KEY, null).isEmpty() || + FindProperties.find(p, WRAPPER_SHA_256_SUM_KEY, null).isEmpty()) { + acc.needsWrapperUpdate = true; + } + return p; + } + + @Override + public Properties visitEntry(Properties.Entry entry, ExecutionContext ctx) { + MavenWrapper mavenWrapper = getMavenWrapper(ctx); + if ("distributionUrl".equals(entry.getKey())) { + // Typical example: https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + String currentDistributionUrl = entry.getValue().getText(); + if (!mavenWrapper.getPropertiesFormattedDistributionUrl().equals(currentDistributionUrl)) { + acc.needsWrapperUpdate = true; + } + } else if ("wrapperUrl".equals(entry.getKey())) { + // Typical example: https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar + String currentWrapperUrl = entry.getValue().getText(); + if (!mavenWrapper.getPropertiesFormattedWrapperUrl().equals(currentWrapperUrl)) { + acc.needsWrapperUpdate = true; + } + } + return entry; + } + }, + new TreeVisitor() { + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + if (!super.isAcceptable(sourceFile, ctx)) { + return false; + } + + MavenWrapper mavenWrapper = getMavenWrapper(ctx); + + if (sourceFile instanceof Quark || sourceFile instanceof Remote) { + if (equalIgnoringSeparators(sourceFile.getSourcePath(), WRAPPER_JAR_LOCATION)) { + acc.addMavenWrapperJar = false; + if (mavenWrapper.getWrapperDistributionType() != DistributionType.Bin) { + acc.needsWrapperUpdate = true; + } + return true; + } else if (equalIgnoringSeparators(sourceFile.getSourcePath(), WRAPPER_DOWNLOADER_LOCATION)) { + acc.addMavenWrapperDownloader = false; + if (mavenWrapper.getWrapperDistributionType() != DistributionType.Source) { + acc.needsWrapperUpdate = true; + } + return true; + } + } + + if (sourceFile instanceof PlainText) { + if (equalIgnoringSeparators(sourceFile.getSourcePath(), WRAPPER_BATCH_LOCATION)) { + acc.addMavenBatchScript = false; + return true; + } else if (equalIgnoringSeparators(sourceFile.getSourcePath(), WRAPPER_SCRIPT_LOCATION)) { + acc.addMavenShellScript = false; + return true; + } + } + + return false; + } + } + ); + } + + @Override + public Collection generate(MavenWrapperState acc, ExecutionContext ctx) { + if (Boolean.FALSE.equals(addIfMissing)) { + return Collections.emptyList(); + } + + MavenWrapper mavenWrapper = getMavenWrapper(ctx); + if (mavenWrapper.getWrapperDistributionType() == DistributionType.Bin) { + if (!(acc.addMavenWrapperJar || acc.addMavenWrapperProperties || acc.addMavenBatchScript || acc.addMavenShellScript)) { + return Collections.emptyList(); + } + } else if (mavenWrapper.getWrapperDistributionType() == DistributionType.OnlyScript) { + if (!(acc.addMavenWrapperProperties || acc.addMavenBatchScript || acc.addMavenShellScript)) { + return Collections.emptyList(); + } + } else { + if (!(acc.addMavenWrapperDownloader || acc.addMavenWrapperProperties || acc.addMavenBatchScript || acc.addMavenShellScript)) { + return Collections.emptyList(); + } + } + + List mavenWrapperFiles = new ArrayList<>(); + ZonedDateTime now = ZonedDateTime.now(); + + if (acc.addMavenWrapperProperties) { + String mavenWrapperPropertiesText = ASF_LICENSE_HEADER + + DISTRIBUTION_URL_KEY + "=" + mavenWrapper.getPropertiesFormattedDistributionUrl() + "\n" + + DISTRIBUTION_SHA_256_SUM_KEY + "=" + mavenWrapper.getDistributionChecksum().getHexValue(); + if (mavenWrapper.getWrapperDistributionType() != DistributionType.OnlyScript) { + mavenWrapperPropertiesText += "\n" + + WRAPPER_URL_KEY + "=" + mavenWrapper.getPropertiesFormattedWrapperUrl() + "\n" + + WRAPPER_SHA_256_SUM_KEY + "=" + mavenWrapper.getWrapperChecksum().getHexValue(); + } + //noinspection UnusedProperty + Properties.File mavenWrapperProperties = new PropertiesParser().parse(mavenWrapperPropertiesText) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Could not parse as properties")) + .withSourcePath(WRAPPER_PROPERTIES_LOCATION); + mavenWrapperFiles.add(mavenWrapperProperties); + } + + FileAttributes wrapperScriptAttributes = new FileAttributes(now, now, now, true, true, true, 1L); + if (acc.addMavenShellScript) { + String mvnwText = unixScript(mavenWrapper, ctx); + PlainText mvnw = PlainText.builder() + .text(mvnwText) + .sourcePath(WRAPPER_SCRIPT_LOCATION) + .fileAttributes(wrapperScriptAttributes) + .build(); + mavenWrapperFiles.add(mvnw); + } + + if (acc.addMavenBatchScript) { + String mvnwCmdText = batchScript(mavenWrapper, ctx); + PlainText mvnwCmd = PlainText.builder() + .text(mvnwCmdText) + .sourcePath(WRAPPER_BATCH_LOCATION) + .fileAttributes(wrapperScriptAttributes) + .build(); + mavenWrapperFiles.add(mvnwCmd); + } + + if (mavenWrapper.getWrapperDistributionType() == DistributionType.Bin && acc.addMavenWrapperJar) { + mavenWrapperFiles.add(mavenWrapper.wrapperJar()); + } else if (mavenWrapper.getWrapperDistributionType() == DistributionType.Source && acc.addMavenWrapperDownloader) { + mavenWrapperFiles.add(mavenWrapper.wrapperDownloader()); + } + + return mavenWrapperFiles; + } + + @Override + public TreeVisitor getVisitor(MavenWrapperState acc) { + if (!acc.needsWrapperUpdate) { + return TreeVisitor.noop(); + } + + return new TreeVisitor() { + @Override + public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (!(tree instanceof SourceFile)) { + return tree; + } + + SourceFile sourceFile = (SourceFile) tree; + if (acc.updatedMarker != null) { + sourceFile = sourceFile.getMarkers().findFirst(BuildTool.class) + .map(buildTool -> (SourceFile) tree.withMarkers(tree.getMarkers().setByType(acc.updatedMarker))) + .orElse(sourceFile); + } + + MavenWrapper mavenWrapper = getMavenWrapper(ctx); + + if (sourceFile instanceof PlainText && PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_SCRIPT_LOCATION_RELATIVE_PATH)) { + String mvnwText = unixScript(mavenWrapper, ctx); + PlainText mvnw = (PlainText) setExecutable(sourceFile); + if (!mvnwText.equals(mvnw.getText())) { + mvnw = mvnw.withText(mvnwText); + } + return mvnw; + } + if (sourceFile instanceof PlainText && PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_BATCH_LOCATION_RELATIVE_PATH)) { + String mvnwCmdText = batchScript(mavenWrapper, ctx); + PlainText mvnwCmd = (PlainText) setExecutable(sourceFile); + if (!mvnwCmdText.equals(mvnwCmd.getText())) { + mvnwCmd = mvnwCmd.withText(mvnwCmdText); + } + return mvnwCmd; + } + + if (sourceFile instanceof Properties.File && PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_PROPERTIES_LOCATION_RELATIVE_PATH)) { + return new WrapperPropertiesVisitor(mavenWrapper).visitNonNull(sourceFile, ctx); + } + if (mavenWrapper.getWrapperDistributionType() == DistributionType.Bin) { + if ((sourceFile instanceof Quark || sourceFile instanceof Remote) && PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_JAR_LOCATION_RELATIVE_PATH)) { + return mavenWrapper.wrapperJar().withId(sourceFile.getId()).withMarkers(sourceFile.getMarkers()); + } + + if (PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_DOWNLOADER_LOCATION_RELATIVE_PATH)) { + return null; + } + } else if (mavenWrapper.getWrapperDistributionType() == DistributionType.Source) { + if ((sourceFile instanceof Quark || sourceFile instanceof Remote) && PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_DOWNLOADER_LOCATION_RELATIVE_PATH)) { + return mavenWrapper.wrapperDownloader().withId(sourceFile.getId()).withMarkers(sourceFile.getMarkers()); + } + + if (PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_JAR_LOCATION_RELATIVE_PATH)) { + return null; + } + } else if (PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_JAR_LOCATION_RELATIVE_PATH) || + PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_DOWNLOADER_LOCATION_RELATIVE_PATH)) { + return null; + } + return sourceFile; + } + }; + } + + private static T setExecutable(T sourceFile) { + FileAttributes attributes = sourceFile.getFileAttributes(); + if (attributes == null) { + ZonedDateTime now = ZonedDateTime.now(); + return sourceFile.withFileAttributes(new FileAttributes(now, now, now, true, true, true, 1L)); + } else if (!attributes.isExecutable()) { + return sourceFile.withFileAttributes(attributes.withExecutable(true)); + } + return sourceFile; + } + + private String unixScript(MavenWrapper mavenWrapper, ExecutionContext ctx) { + return StringUtils.readFully(mavenWrapper.mvnw().getInputStream(ctx)); + } + + private String batchScript(MavenWrapper mavenWrapper, ExecutionContext ctx) { + return StringUtils.readFully(mavenWrapper.mvnwCmd().getInputStream(ctx)); + } + + @AllArgsConstructor + private static class WrapperPropertiesVisitor extends PropertiesIsoVisitor { + MavenWrapper mavenWrapper; + + @Override + public Properties.File visitFile(Properties.File file, ExecutionContext executionContext) { + Properties.File p = super.visitFile(file, executionContext); + Checksum mavenDistributionChecksum = mavenWrapper.getDistributionChecksum(); + if (FindProperties.find(p, DISTRIBUTION_SHA_256_SUM_KEY, null).isEmpty() && mavenDistributionChecksum != null) { + Properties.Value propertyValue = new Properties.Value(Tree.randomId(), "", Markers.EMPTY, mavenDistributionChecksum.getHexValue()); + Properties.Entry entry = new Properties.Entry(Tree.randomId(), "\n", Markers.EMPTY, DISTRIBUTION_SHA_256_SUM_KEY, "", Properties.Entry.Delimiter.EQUALS, propertyValue); + p = p.withContent(ListUtils.concat(p.getContent(), entry)); + } + if (mavenWrapper.getWrapperDistributionType() != DistributionType.OnlyScript) { + Checksum wrapperJarChecksum = mavenWrapper.getWrapperChecksum(); + if (FindProperties.find(p, WRAPPER_SHA_256_SUM_KEY, null).isEmpty() && wrapperJarChecksum != null) { + Properties.Value propertyValue = new Properties.Value(Tree.randomId(), "", Markers.EMPTY, wrapperJarChecksum.getHexValue()); + Properties.Entry entry = new Properties.Entry(Tree.randomId(), "\n", Markers.EMPTY, WRAPPER_SHA_256_SUM_KEY, "", Properties.Entry.Delimiter.EQUALS, propertyValue); + p = p.withContent(ListUtils.concat(p.getContent(), entry)); + } + } + return p; + } + + @Override + @Nullable + public Properties.Entry visitEntry(Properties.Entry entry, ExecutionContext executionContext) { + if (DISTRIBUTION_URL_KEY.equals(entry.getKey())) { + Properties.Value value = entry.getValue(); + if (!mavenWrapper.getPropertiesFormattedDistributionUrl().equals(value.getText())) { + return entry.withValue(value.withText(mavenWrapper.getPropertiesFormattedDistributionUrl())); + } + } else if (DISTRIBUTION_SHA_256_SUM_KEY.equals(entry.getKey())) { + Properties.Value value = entry.getValue(); + Checksum mavenDistributionChecksum = mavenWrapper.getDistributionChecksum(); + if (mavenDistributionChecksum != null && !mavenDistributionChecksum.getHexValue().equals(value.getText())) { + return entry.withValue(value.withText(mavenDistributionChecksum.getHexValue())); + } + } else if (WRAPPER_URL_KEY.equals(entry.getKey())) { + if (mavenWrapper.getWrapperDistributionType() != DistributionType.OnlyScript) { + Properties.Value value = entry.getValue(); + if (!mavenWrapper.getPropertiesFormattedWrapperUrl().equals(value.getText())) { + return entry.withValue(value.withText(mavenWrapper.getPropertiesFormattedWrapperUrl())); + } + } else { + return null; + } + } else if (WRAPPER_SHA_256_SUM_KEY.equals(entry.getKey())) { + if (mavenWrapper.getWrapperDistributionType() != DistributionType.OnlyScript) { + Properties.Value value = entry.getValue(); + Checksum wrapperJarChecksum = mavenWrapper.getWrapperChecksum(); + if (wrapperJarChecksum != null && !wrapperJarChecksum.getHexValue().equals(value.getText())) { + return entry.withValue(value.withText(wrapperJarChecksum.getHexValue())); + } + } else { + return null; + } + } + return entry; + } + } +} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java new file mode 100644 index 00000000000..555f6e1d50d --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java @@ -0,0 +1,216 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://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 org.openrewrite.maven.utilities; + +import lombok.Value; +import org.openrewrite.Checksum; +import org.openrewrite.ExecutionContext; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.maven.MavenDownloadingException; +import org.openrewrite.maven.internal.MavenPomDownloader; +import org.openrewrite.maven.tree.GroupArtifact; +import org.openrewrite.maven.tree.MavenMetadata; +import org.openrewrite.maven.tree.MavenRepository; +import org.openrewrite.remote.Remote; +import org.openrewrite.semver.LatestRelease; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; + +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static java.util.Objects.requireNonNull; + +@Value +public class MavenWrapper { + public static final String ASF_LICENSE_HEADER = "# Licensed to the Apache Software Foundation (ASF) under one\n" + + "# or more contributor license agreements. See the NOTICE file\n" + + "# distributed with this work for additional information\n" + + "# regarding copyright ownership. The ASF licenses this file\n" + + "# to you under the Apache License, Version 2.0 (the\n" + + "# \"License\"); you may not use this file except in compliance\n" + + "# with the License. You may obtain a copy of the License at\n" + + "# \n" + + "# http://www.apache.org/licenses/LICENSE-2.0\n" + + "# \n" + + "# Unless required by applicable law or agreed to in writing,\n" + + "# software distributed under the License is distributed on an\n" + + "# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n" + + "# KIND, either express or implied. See the License for the\n" + + "# specific language governing permissions and limitations\n" + + "# under the License.\n"; + public static final String WRAPPER_DOWNLOADER_LOCATION_RELATIVE_PATH = ".mvn/wrapper/MavenWrapperDownloader.java"; + public static final String WRAPPER_JAR_LOCATION_RELATIVE_PATH = ".mvn/wrapper/maven-wrapper.jar"; + public static final String WRAPPER_PROPERTIES_LOCATION_RELATIVE_PATH = ".mvn/wrapper/maven-wrapper.properties"; + public static final String WRAPPER_SCRIPT_LOCATION_RELATIVE_PATH = "mvnw"; + public static final String WRAPPER_BATCH_LOCATION_RELATIVE_PATH = "mvnw.cmd"; + + public static final Path WRAPPER_DOWNLOADER_LOCATION = Paths.get(WRAPPER_DOWNLOADER_LOCATION_RELATIVE_PATH); + public static final Path WRAPPER_JAR_LOCATION = Paths.get(WRAPPER_JAR_LOCATION_RELATIVE_PATH); + public static final Path WRAPPER_PROPERTIES_LOCATION = Paths.get(WRAPPER_PROPERTIES_LOCATION_RELATIVE_PATH); + public static final Path WRAPPER_SCRIPT_LOCATION = Paths.get(WRAPPER_SCRIPT_LOCATION_RELATIVE_PATH); + public static final Path WRAPPER_BATCH_LOCATION = Paths.get(WRAPPER_BATCH_LOCATION_RELATIVE_PATH); + + String wrapperVersion; + String wrapperUri; + Checksum wrapperChecksum; + String wrapperDistributionUri; + DistributionType wrapperDistributionType; + String distributionVersion; + String distributionUri; + Checksum distributionChecksum; + + public static MavenWrapper create( + @Nullable String wrapperVersion, + @Nullable String wrapperDistributionTypeName, + @Nullable String distributionVersion, + @Nullable String repositoryUrl, + ExecutionContext ctx + ) { + DistributionType wrapperDistributionType = Arrays.stream(DistributionType.values()) + .filter(dt -> dt.classifier.equalsIgnoreCase(wrapperDistributionTypeName)) + .findAny() + .orElse(DistributionType.Bin); + + MavenPomDownloader pomDownloader = new MavenPomDownloader(Collections.emptyMap(), ctx, null, null); + + VersionComparator wrapperVersionComparator = (wrapperVersion == null) ? + new LatestRelease(null) : + requireNonNull(Semver.validate(wrapperVersion, null).getValue()); + VersionComparator distributionVersionComparator = (distributionVersion == null) ? + new LatestRelease(null) : + requireNonNull(Semver.validate(distributionVersion, null).getValue()); + + MavenRepository repository = repositoryUrl == null ? + MavenRepository.MAVEN_CENTRAL : + MavenRepository.builder() + .uri(repositoryUrl) + .releases(true) + .snapshots(true) + .build(); + List repositories = Collections.singletonList(repository); + try { + GroupArtifact wrapperDistributionGroupArtifact = new GroupArtifact("org.apache.maven.wrapper", "maven-wrapper-distribution"); + MavenMetadata wrapperMetadata = pomDownloader.downloadMetadata(wrapperDistributionGroupArtifact, null, repositories); + String resolvedWrapperVersion = wrapperMetadata.getVersioning() + .getVersions() + .stream() + .filter(v -> wrapperVersionComparator.isValid(null, v)) + .max((v1, v2) -> wrapperVersionComparator.compare(null, v1, v2)) + .orElseThrow(() -> new IllegalStateException("Expected to find at least one Maven wrapper version to select from.")); + String resolvedWrapperUri = getDownloadUriFor(repository, new GroupArtifact("org.apache.maven.wrapper", "maven-wrapper"), resolvedWrapperVersion, null, "jar"); + String resolvedWrapperDistributionUri = getDownloadUriFor(repository, wrapperDistributionGroupArtifact, resolvedWrapperVersion, wrapperDistributionType.classifier, "zip"); + + GroupArtifact distributionGroupArtifact = new GroupArtifact("org.apache.maven", "apache-maven"); + MavenMetadata distributionMetadata = pomDownloader.downloadMetadata(distributionGroupArtifact, null, repositories); + String resolvedDistributionVersion = distributionMetadata.getVersioning() + .getVersions() + .stream() + .filter(v -> distributionVersionComparator.isValid(null, v)) + .max((v1, v2) -> distributionVersionComparator.compare(null, v1, v2)) + .orElseThrow(() -> new IllegalStateException("Expected to find at least one Maven distribution version to select from.")); + String resolvedDistributionUri = getDownloadUriFor(repository, distributionGroupArtifact, resolvedDistributionVersion, "bin", "zip"); + + Remote wrapperJar = (Remote) Checksum.sha256(Remote.builder( + WRAPPER_JAR_LOCATION, + URI.create(resolvedWrapperUri) + ).build()); + + Remote mavenDistribution = (Remote) Checksum.sha256(Remote.builder( + Paths.get(""), + URI.create(resolvedDistributionUri) + ).build()); + + return new MavenWrapper( + resolvedWrapperVersion, + resolvedWrapperUri, + wrapperJar.getChecksum(), + resolvedWrapperDistributionUri, + wrapperDistributionType, + resolvedDistributionVersion, + resolvedDistributionUri, + mavenDistribution.getChecksum() + ); + } catch (MavenDownloadingException e) { + throw new RuntimeException("Could not get Maven versions at: " + repository.getUri(), e); + } + } + + public String getPropertiesFormattedWrapperUrl() { + return wrapperUri.replaceAll("(? + * 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 + *

+ * https://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 org.openrewrite.maven; + +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Test; +import org.openrewrite.*; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.ipc.http.HttpUrlConnectionSender; +import org.openrewrite.marker.BuildTool; +import org.openrewrite.maven.utilities.MavenWrapper; +import org.openrewrite.remote.Remote; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpecs; +import org.openrewrite.text.PlainText; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.*; +import java.time.Duration; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.UnaryOperator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.openrewrite.maven.utilities.MavenWrapper.*; +import static org.openrewrite.properties.Assertions.properties; +import static org.openrewrite.test.SourceSpecs.*; + +class UpdateMavenWrapperTest implements RewriteTest { + private final UnaryOperator<@Nullable String> notEmpty = actual -> { + assertThat(actual).isNotNull(); + return actual + "\n"; + }; + + // Maven wrapper script text for 3.1.1 + private static final String MVNW_TEXT = StringUtils.readFully(UpdateMavenWrapperTest.class.getResourceAsStream("/mvnw")); + private static final String MVNW_CMD_TEXT = StringUtils.readFully(UpdateMavenWrapperTest.class.getResourceAsStream("/mvnw.cmd")); + + private final SourceSpecs mvnw = text("", spec -> spec.path(WRAPPER_SCRIPT_LOCATION).after(notEmpty)); + private final SourceSpecs mvnwCmd = text("", spec -> spec.path(WRAPPER_BATCH_LOCATION).after(notEmpty)); + private final SourceSpecs mvnWrapperJarQuark = other("", spec -> spec.path(WRAPPER_JAR_LOCATION).after(notEmpty)); + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new UpdateMavenWrapper("3.1.x", null, "3.8.x", null, null)); + } + + @Test + @DocumentExample("Add a new Maven wrapper") + void addMavenWrapper() { + rewriteRun( + spec -> spec.afterRecipe(run -> { + assertThat(run.getChangeset().getAllResults()).hasSize(4); + + var mvnw = result(run, PlainText.class, "mvnw"); + assertThat(mvnw.getSourcePath()).isEqualTo(WRAPPER_SCRIPT_LOCATION); + assertThat(mvnw.getText()).isEqualTo(MVNW_TEXT); + assertThat(mvnw.getFileAttributes()).isNotNull(); + assertThat(mvnw.getFileAttributes().isReadable()).isTrue(); + assertThat(mvnw.getFileAttributes().isWritable()).isTrue(); + + var mvnwCmd = result(run, PlainText.class, "mvnw.cmd"); + assertThat(mvnwCmd.getSourcePath()).isEqualTo(WRAPPER_BATCH_LOCATION); + assertThat(mvnwCmd.getText()).isEqualTo(MVNW_CMD_TEXT); + + var mavenWrapperJar = result(run, Remote.class, "maven-wrapper.jar"); + assertThat(mavenWrapperJar.getSourcePath()).isEqualTo(WRAPPER_JAR_LOCATION); + assertThat(mavenWrapperJar.getUri()).isEqualTo(URI.create("https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar")); + assertThat(isValidWrapperJar(mavenWrapperJar)).as("Wrapper jar is not valid").isTrue(); + }), + properties( + null, + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ) + ); + } + + @Test + @DocumentExample("Update existing Maven wrapper") + void updateWrapper() { + rewriteRun( + spec -> spec.allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.0"))) + .afterRecipe(run -> { + var mvnw = result(run, PlainText.class, "mvnw"); + assertThat(mvnw.getSourcePath()).isEqualTo(WRAPPER_SCRIPT_LOCATION); + assertThat(mvnw.getText()).isEqualTo(MVNW_TEXT); + assertThat(mvnw.getFileAttributes()).isNotNull(); + assertThat(mvnw.getFileAttributes().isReadable()).isTrue(); + assertThat(mvnw.getFileAttributes().isWritable()).isTrue(); + + var mvnwCmd = result(run, PlainText.class, "mvnw.cmd"); + assertThat(mvnwCmd.getSourcePath()).isEqualTo(WRAPPER_BATCH_LOCATION); + assertThat(mvnwCmd.getText()).isEqualTo(MVNW_CMD_TEXT); + + var mavenWrapperJar = result(run, Remote.class, "maven-wrapper.jar"); + assertThat(mavenWrapperJar.getSourcePath()).isEqualTo(WRAPPER_JAR_LOCATION); + assertThat(mavenWrapperJar.getUri()).isEqualTo(URI.create("https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar")); + assertThat(isValidWrapperJar(mavenWrapperJar)).as("Wrapper jar is not valid").isTrue(); + }), + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar + """), + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + .afterRecipe(mavenWrapperProperties -> + assertThat(mavenWrapperProperties.getMarkers().findFirst(BuildTool.class)).hasValueSatisfying(buildTool -> { + assertThat(buildTool.getType()).isEqualTo(BuildTool.Type.Maven); + assertThat(buildTool.getVersion()).isEqualTo("3.8.8"); + })) + ), + mvnw, + mvnwCmd, + mvnWrapperJarQuark + ); + } + + @Test + void updateVersionUsingSourceDistribution() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenWrapper("3.1.x", "source", "3.8.x", null, null)) + .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.0"))) + .afterRecipe(run -> { + var mvnw = result(run, PlainText.class, "mvnw"); + assertThat(mvnw.getText()).isNotBlank(); + + var mvnwCmd = result(run, PlainText.class, "mvnw.cmd"); + assertThat(mvnwCmd.getText()).isNotBlank(); + + assertThatThrownBy(() -> result(run, SourceFile.class, "maven-wrapper.jar")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + + var mvnwDownloaderJava = result(run, Remote.class, "MavenWrapperDownloader.java"); + assertThat(mvnwDownloaderJava.getSourcePath()).isEqualTo(WRAPPER_DOWNLOADER_LOCATION); + assertThat(mvnwDownloaderJava.getUri()).isEqualTo(URI.create("https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper-distribution/3.1.1/maven-wrapper-distribution-3.1.1-source.zip")); + }), + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar + """), + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + mvnw, + mvnwCmd, + other( + "", + null, + spec -> spec.path(".mvn/wrapper/maven-wrapper.jar") + ) + ); + } + + @Test + void updateVersionUsingScriptDistribution() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenWrapper("3.1.x", "script", "3.8.x", null, null)) + .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.0"))) + .afterRecipe(run -> { + var mvnw = result(run, PlainText.class, "mvnw"); + assertThat(mvnw.getText()).isNotBlank(); + + var mvnwCmd = result(run, PlainText.class, "mvnw.cmd"); + assertThat(mvnwCmd.getText()).isNotBlank(); + + assertThatThrownBy(() -> result(run, SourceFile.class, "maven-wrapper.jar")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + + assertThatThrownBy(() -> result(run, Remote.class, "MavenWrapperDownloader.java")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + }), + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar + """), + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + mvnw, + mvnwCmd, + other( + "", + null, + spec -> spec.path(".mvn/wrapper/maven-wrapper.jar") + ) + ); + } + + @Test + void updateVersionUsingOnlyScriptDistribution() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenWrapper(null, "only-script", "3.8.x", null, null)) + .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.0"))) + .afterRecipe(run -> { + var mvnw = result(run, PlainText.class, "mvnw"); + assertThat(mvnw.getText()).isNotBlank(); + + var mvnwCmd = result(run, PlainText.class, "mvnw.cmd"); + assertThat(mvnwCmd.getText()).isNotBlank(); + + assertThatThrownBy(() -> result(run, Remote.class, "maven-wrapper.jar")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + + assertThatThrownBy(() -> result(run, Remote.class, "MavenWrapperDownloader.java")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + }), + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + """), + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + mvnw, + mvnwCmd, + other( + "", + null, + spec -> spec.path(".mvn/wrapper/maven-wrapper.jar") + ) + ); + } + + @Test + void dontAddMissingWrapper() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenWrapper("3.1.x", null, "3.8.x", null, Boolean.FALSE)) + .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.0"))) + .afterRecipe(run -> assertThat(run.getChangeset().getAllResults()).isEmpty()) + ); + } + + @Test + void updateMultipleWrappers() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenWrapper("3.1.x", null, "3.8.x", null, Boolean.FALSE)) + .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.0"))), + dir("example1", + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar + """), + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + mvnw, + mvnwCmd, + mvnWrapperJarQuark + ), + dir("example2", + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar + """), + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + mvnw, + mvnwCmd, + mvnWrapperJarQuark + ) + ); + } + + @Test + void doNotDowngrade() { + rewriteRun( + spec -> spec.allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.9.0"))), + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.0/apache-maven-3.9.0-bin.zip + wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + text("", spec -> spec.path("mvnw")), + text("", spec -> spec.path("mvnw.cmd")), + other("", spec -> spec.path(".mvn/wrapper/maven-wrapper.jar")) + ); + } + + @Test + void allowUpdatingDistributionTypeWhenSameVersion() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenWrapper("3.1.x", "script", "3.8.x", null, null)) + .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.8"))) + .afterRecipe(run -> { + var mvnw = result(run, PlainText.class, "mvnw"); + assertThat(mvnw.getText()).isNotBlank(); + + var mvnwCmd = result(run, PlainText.class, "mvnw.cmd"); + assertThat(mvnwCmd.getText()).isNotBlank(); + + assertThatThrownBy(() -> result(run, SourceFile.class, "maven-wrapper.jar")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + + assertThatThrownBy(() -> result(run, SourceFile.class, "MavenWrapperDownloader.java")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + }), + properties( + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + mvnw, + mvnwCmd, + other( + "", + null, + spec -> spec.path(".mvn/wrapper/maven-wrapper.jar") + ), + other( + "", + null, + spec -> spec.path(".mvn/wrapper/MavenWrapperDownloader.java") + ) + ); + } + + private String withLicenseHeader(@Language("properties") String original) { + return MavenWrapper.ASF_LICENSE_HEADER + original; + } + + private S result(RecipeRun run, Class clazz, String endsWith) { + return run.getChangeset().getAllResults().stream() + .map(Result::getAfter) + .filter(Objects::nonNull) + .filter(r -> r.getSourcePath().endsWith(endsWith)) + .findFirst() + .map(clazz::cast) + .orElseThrow(); + } + + private boolean isValidWrapperJar(Remote gradleWrapperJar) { + try { + Path testWrapperJar = Files.createTempFile("maven-wrapper", "jar"); + ExecutionContext ctx = new InMemoryExecutionContext(); + HttpSenderExecutionContextView.view(ctx).setHttpSender(new HttpUrlConnectionSender(Duration.ofSeconds(5), Duration.ofSeconds(5))); + try (InputStream is = gradleWrapperJar.getInputStream(ctx)) { + Files.copy(is, testWrapperJar, StandardCopyOption.REPLACE_EXISTING); + try (FileSystem fs = FileSystems.newFileSystem(testWrapperJar)) { + return Files.exists(fs.getPath("org/apache/maven/wrapper/MavenWrapperMain.class")); + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/rewrite-maven/src/test/resources/mvnw b/rewrite-maven/src/test/resources/mvnw new file mode 100644 index 00000000000..b7f064624f8 --- /dev/null +++ b/rewrite-maven/src/test/resources/mvnw @@ -0,0 +1,287 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.1.1 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + printf '%s' "$(cd "$basedir"; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname $0)") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $wrapperUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + QUIET="--quiet" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + elif command -v curl > /dev/null; then + QUIET="--silent" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=`cygpath --path --windows "$javaSource"` + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/rewrite-maven/src/test/resources/mvnw.cmd b/rewrite-maven/src/test/resources/mvnw.cmd new file mode 100644 index 00000000000..474c9d6b74c --- /dev/null +++ b/rewrite-maven/src/test/resources/mvnw.cmd @@ -0,0 +1,187 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.1.1 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE%