Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: openrewrite/rewrite
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v8.42.5
Choose a base ref
...
head repository: openrewrite/rewrite
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on Jan 3, 2025

  1. Fix parsing string literal with line breaks and spaces (#4843)

    * Fix parsing of string literals with linebreaks and spaces
    
    * Use `getDelimiter` for GStringExpression
    
    * Improve stringLiteralInParentheses test
    
    * Remove temporary moreParenthesesStuff test
    
    * Revert escapeCharacters test
    
    * Remove `lengthAccordingToAst`, use same trick for ConstantExpression in GString
    
    * Fix typo
    jevanlingen authored Jan 3, 2025
    Copy the full SHA
    8a9a449 View commit details
  2. Add TOML language parser (#4845)

    * Add TOML language parser
    
    * Polish and address bot review comments
    
    * Apply suggestions from code review
    
    Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
    
    * Update rewrite-toml/src/main/java/org/openrewrite/toml/internal/grammar/TomlParserVisitor.java
    
    Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
    
    ---------
    
    Co-authored-by: Tim te Beek <tim@moderne.io>
    Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
    3 people authored Jan 3, 2025
    Copy the full SHA
    02411bd View commit details
  3. Polish toml

    shanman190 committed Jan 3, 2025
    Copy the full SHA
    19dfd6a View commit details
  4. 2
    Copy the full SHA
    6dd029f View commit details
  5. Adding option cutOffDate to RemoveOwaspSuppressions (#4846)

    * Adding option cutOffDate to RemoveOwaspSuppressions
    
    There are cases were we want to only remove suppressions up to a specific date, this allows a cut off date to be provided when removed expired suppressions
    
    * Update rewrite-xml/src/main/java/org/openrewrite/xml/security/RemoveOwaspSuppressions.java
    
    Co-authored-by: Tim te Beek <tim@moderne.io>
    
    ---------
    
    Co-authored-by: Tim te Beek <tim@moderne.io>
    lkerford and timtebeek authored Jan 3, 2025
    Copy the full SHA
    e6a8499 View commit details

Commits on Jan 6, 2025

  1. Copy the full SHA
    c455436 View commit details
  2. Skip parsing groovy generated transform methods (#4848)

    * Skip parsing groovy generated transform methods
    
    * Skip parsing groovy generated transform methods
    
    * Skip parsing groovy generated transform methods
    
    * Skip parsing groovy generated transform methods
    
    * improvement
    
    * improvement
    
    * improvement
    
    * improvement
    jevanlingen authored Jan 6, 2025
    Copy the full SHA
    a3249f2 View commit details
  3. Copy the full SHA
    a1d0f36 View commit details
  4. Copy the full SHA
    28d88c4 View commit details
  5. Use our StringUtils

    sambsnyd committed Jan 6, 2025
    Copy the full SHA
    621dcfa View commit details

Commits on Jan 7, 2025

  1. Add recipe to enable Develocity build cache in xml configuration (#4856)

    * Add enable buildcache recipe
    
    * Polish
    
    * Use `<local><enabled>`
    
    * Rename recipe to be Develocity specific, to avoid confusion with Maven build cache
    
    ---------
    
    Co-authored-by: Tim te Beek <tim@moderne.io>
    nielsdebruin and timtebeek authored Jan 7, 2025
    Copy the full SHA
    15f7751 View commit details
  2. HCL - comments in multilines for (#4858)

    * UT for #4857
    
    * Fixing lexer to accept comemnts within for expressions
    
    * licenseFormat
    greg-at-moderne authored Jan 7, 2025
    Copy the full SHA
    fd71955 View commit details
  3. Copy the full SHA
    b3f17ce View commit details
  4. Lombok support for java 21 (#4860)

    * Lombok support for java 21
    
    * Minimize diff with Java 17 to make it easier to spot intentional changes
    
    * Minimize diff some more to add missing `var` handling
    
    ---------
    
    Co-authored-by: Tim te Beek <tim@moderne.io>
    jevanlingen and timtebeek authored Jan 7, 2025
    Copy the full SHA
    6ddcc37 View commit details
  5. HCL - comments as the final lines (#4861)

    * Support final line being a comment
    
    * licenseFormat
    greg-at-moderne authored Jan 7, 2025
    Copy the full SHA
    0e5b439 View commit details

Commits on Jan 8, 2025

  1. Copy the full SHA
    c2e5624 View commit details
  2. Copy the full SHA
    789ac5f View commit details
  3. Mark PlainText all-args constructor as private

    Add back the previous constructor without the `SoftReference<References>` as public.
    knutwannheden committed Jan 8, 2025
    Copy the full SHA
    6bfac94 View commit details
  4. Check for null in TabsAndIndentsVisitor#visitContainer()

    In case the caller doesn't have a guard on this.
    knutwannheden committed Jan 8, 2025
    Copy the full SHA
    3b3a56c View commit details
  5. Copy the full SHA
    13b6168 View commit details

Commits on Jan 9, 2025

  1. fix: create dependencyResourceLoaders in 2 passes (#4870)

    * fix: create dependencyResourceLoaders in 2 passes
    
    * Inline variables used once
    
    ---------
    
    Co-authored-by: Tim te Beek <tim@moderne.io>
    natedanner and timtebeek authored Jan 9, 2025
    Copy the full SHA
    1bb9da0 View commit details
  2. HCL - Fixing empty comment handling (#4871)

    * Fixing empty comment handling
    
    * Newline
    
    Co-authored-by: Tim te Beek <tim@moderne.io>
    
    ---------
    
    Co-authored-by: Tim te Beek <tim@moderne.io>
    greg-at-moderne and timtebeek authored Jan 9, 2025
    Copy the full SHA
    7566b00 View commit details
  3. Copy the full SHA
    0826417 View commit details
  4. Copy the full SHA
    d11eae9 View commit details
  5. Map trailing commas as TrailingComma Marker, to fix J.Erroneous

    … issues seen (#4869)
    
    * Do not allow J.Erroneous LST nodes by default in tests
    
    * Allow erroneous nodes in FindCompileErrorsTest
    
    * Use `TrailingComma` marker for enum values and array initializers
    
    Add an overload for `ReloadableJava17ParserVisitor#convert()` which allows supplying a `Markers` function, so that we can capture trailing commas using a `TrailingComma` marker and then also avoid the issue with the `J.Erroneous` getting constructed.
    
    * Update whitespace handling for last enum constant
    
    The last enum constant should only be right-padded if it has a trailing comma or semicolon. This is important because the whitespace after it belongs to the prefix of the next statement or the `J.Block#end`.
    
    * Allow errorneous nodes in specific tests
    
    * Update JavaDoc for typeValidation.erroneous after feedback
    
    * Use overloaded method
    
    * Apply to Java 21 parser as well
    
    * Format Java 21 parser
    
    * Apply to Java 11 parser as well
    
    * Apply to Java 8 parser as well
    
    * Fix failing tests
    
    * Minimize diff between versions to make it easier to compare
    
    ---------
    
    Co-authored-by: Knut Wannheden <knut@moderne.io>
    Co-authored-by: Laurens Westerlaken <laurens.westerlaken@jdriven.com>
    3 people authored Jan 9, 2025
    Copy the full SHA
    82b61de View commit details
  6. Don't cache filePatterns at Recipe class level... caused strange beha…

    …vior on the saas.
    
    At least I think that's why I was matching too many things with FindSourceFiles on the saas but couldn't reproduce locally.
    sambsnyd committed Jan 9, 2025
    Copy the full SHA
    4c901ff View commit details

Commits on Jan 10, 2025

  1. Fix broken RSPEC links

    sambsnyd committed Jan 10, 2025
    Copy the full SHA
    e69e8ca View commit details
  2. Fix #4877 where the groovy parser would fail to parse variables whose…

    … names started with "def"
    sambsnyd committed Jan 10, 2025
    Copy the full SHA
    fa1fc56 View commit details
  3. Copy the full SHA
    9fc0dba View commit details
  4. Partial support Lombok for java 8 (mostly done) (#4855)

    * Support Lombok for java 8
    
    * Support Lombok for java 8
    
    * Support Lombok for java 8
    
    * Add rewrite-java-lombok to classpath
    
    * Improve message
    
    * Remove `TimedTodo` to be more like the other java parsers
    
    * cleanup
    
    * Add JavaCompiler `delegate` fix
    
    * Support Lomboks `var` and `val` for Java 8
    
    * Cleanup
    
    * Cleanup
    
    * Cleanup of `isLombokGenerated` methods
    
    * Cleanup of `isLombokGenerated` methods
    
    * Cleanup of `isLombokGenerated` methods
    
    * Cleanup of `isLombokGenerated` methods
    
    * Cleanup of `isLombokGenerated` methods
    
    * Cleanup
    
    * Improve `onConstructor` and `onConstructorNoArgs` args
    
    * Fix missing `onConstructor_` check
    
    * Cleanup
    
    * Apply suggestions from code review
    
    Co-authored-by: Knut Wannheden <knut@moderne.io>
    
    * Improve tests
    
    * Merge branch 'main' into lombok-java-8
    
    * Rename JavacAnnotationHandler with no action to XNoOpHandler
    
    * Rename JavacAnnotationHandler with no action to XNoOpHandler
    
    * Improve lomboks `ExampleException` test
    
    ---------
    
    Co-authored-by: Knut Wannheden <knut@moderne.io>
    jevanlingen and knutwannheden authored Jan 10, 2025
    Copy the full SHA
    50d160e View commit details
  5. Copy the full SHA
    6609c8c View commit details
  6. Add recipe to enable Develocity build cache for Gradle (#4859)

    * Add enable buildcache recipe for gradle
    
    * Fix description
    
    * Update test cases and rename config
    
    * Add initial version of visitor
    
    * Update rewrite-gradle/src/main/java/org/openrewrite/gradle/EnableDevelocityBuildCache.java
    
    Co-authored-by: Jacob van Lingen <jacob.van.lingen@moderne.io>
    
    * Indent fix
    
    * Polish
    
    * Change validation
    
    * Remove linebreaks
    
    * Simplify how build cache is created and extracted
    
    * Only look for develocity at the root, and buildCache within that
    
    * Show a richer example for `remotePushEnabled`
    
    ---------
    
    Co-authored-by: Jacob van Lingen <jacob.van.lingen@moderne.io>
    Co-authored-by: Tim te Beek <tim@moderne.io>
    3 people authored Jan 10, 2025
    Copy the full SHA
    6ff7a03 View commit details
  7. Add dark/light svg logo

    For updating various readmes in the future
    mike-solomon committed Jan 10, 2025
    Copy the full SHA
    cd013f4 View commit details
  8. Copy the full SHA
    882291b View commit details
  9. Copy the full SHA
    cb5a059 View commit details
  10. Copy the full SHA
    dd67fe6 View commit details

Commits on Jan 12, 2025

  1. Enable AnnotationMatcher to match values in J.NewArray (#4889)

    * improve `AnnotationMatcher` to match on values in `J.NewArray` initializers values
    
    * update java doc
    MBoegers authored Jan 12, 2025
    Copy the full SHA
    4a8a38b View commit details

Commits on Jan 13, 2025

  1. Print cursor when tests fail to run a recipe (#4891)

    * Print cursor when tests fail to run a recipe
    
    * Fail with the original exception, not a new one
    timtebeek authored Jan 13, 2025
    Copy the full SHA
    6a818cf View commit details
  2. Add TomlVisitorTest after seeing ClassCastException in arrays (#4892)

    * Add TomlVisitorTest after seeing ClassCastException in arrays
    
    * Have Toml.Literal implement TomlValue
    
    * Drop unnecessary cast instead
    timtebeek authored Jan 13, 2025
    Copy the full SHA
    6f3416f View commit details
  3. Lombok's generated @with method misses type information; fix for Java…

    … 17+ (#4882)
    
    * Resolve type information for lombok @with for Java 17+
    
    ---------
    
    Co-authored-by: Laurens Westerlaken <laurens.westerlaken@jdriven.com>
    jevanlingen and Laurens-W authored Jan 13, 2025
    Copy the full SHA
    aeb5bf2 View commit details
  4. Add slf4j-nop transitively through rewrite-test (#4895)

    * Add `slf4j-nop` transitively through rewrite-test
    
    To reduce warnings seen about https://www.slf4j.org/codes.html#StaticLoggerBinder
    
    * Prevent new warning about two implementations
    timtebeek authored Jan 13, 2025
    Copy the full SHA
    30c9156 View commit details
  5. LatestRelease should replace metadataPattern as regex, as documen…

    …ted (#4894)
    
    * Fix version comparator
    
    * LatestRelease.metadataPattern should be matched as a regex
    
    ---------
    
    Co-authored-by: Tim te Beek <tim@moderne.io>
    ZhyliaievD and timtebeek authored Jan 13, 2025
    Copy the full SHA
    cce9f3e View commit details

Commits on Jan 15, 2025

  1. Copy the full SHA
    8a060c6 View commit details
  2. HCL - support for legacy syntax for attribute expressions (#4901)

    * Support for LegacyIndexAttributeExpression
    
    * licenseFormat
    
    * Space.Location.LEGACY_INDEX_ATTRIBUTE_ACCESS
    
    * LegacyIndexAttributeAccess.index is int
    
    * Padding for LegacyAttributeAccess.base
    greg-at-moderne authored Jan 15, 2025
    Copy the full SHA
    db9f188 View commit details
  3. Add new parameter oldAttributeValue to `AddOrUpdateAnnotationAttrib…

    …ute` recipe (#4881)
    
    * Add new parameter `oldAttributeValue` to `AddOrUpdateAnnotationAttribute`
    
    * enhance add test for Enum and class values
    
    * implement checks for `oldAttributeValue`
    
    * small fixes and a reproducer for adding array values to implicite values
    
    * fix handling of implicit and explicit value handling
    
    * fix handling of implicit and explicit value handling in arrays
    
    * Skip supporting source files, and extract constant
    
    * add contract to handle nullability
    
    * remove comments from debugging
    
    ---------
    
    Co-authored-by: Tim te Beek <tim@moderne.io>
    MBoegers and timtebeek authored Jan 15, 2025
    Copy the full SHA
    09dfe06 View commit details
  4. TOML model cleanups (#4907)

    * TOML model cleanups
    
    Added a few missing `@Nullable` annotations and added accessors for `Toml.Table#name`.
    
    * Add two more overrides to `TomlIsoVisitor`
    knutwannheden authored Jan 15, 2025
    Copy the full SHA
    c80874b View commit details

Commits on Jan 16, 2025

  1. When resolving maven pom properties give system properties precedence.

    This matches Maven's own behavior.
    sambsnyd committed Jan 16, 2025
    Copy the full SHA
    2586748 View commit details
  2. Copy the full SHA
    6cd1e2a View commit details
  3. Correct TOML table parsing (#4910)

    * Correct TOML table parsing
    
    Tables in TOML are not nested. I.e. the following should result in an AST with two top-level tables:
    
    ```toml
    [table-1]
    key1 = "some string"
    key2 = 123
    
    [table-2]
    key1 = "another string"
    key2 = 456
    ```
    
    This has now been corrected in the parser.
    
    * Adjust Gradle build script for ANTLR generation
    
    * Add missing line terminators
    knutwannheden authored Jan 16, 2025
    Copy the full SHA
    462dc74 View commit details
  4. Add a test validation that execution context is not mutated during re…

    …cipe execution (#4879)
    
    * Add a test validation that execution context is not mutated during the recipe run.
    
    * Allow messages from MavenExecutionContextView
    sambsnyd authored Jan 16, 2025
    Copy the full SHA
    36efb73 View commit details
Showing 360 changed files with 27,252 additions and 2,868 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<p align="center">
<img src="./doc/logo-oss.png" alt="OpenRewrite"/>
<a href="https://docs.openrewrite.org">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/openrewrite/rewrite/raw/main/doc/logo-oss-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="https://github.com/openrewrite/rewrite/raw/main/doc/logo-oss-light.svg">
<img alt="OpenRewrite Logo" src="https://github.com/openrewrite/rewrite/raw/main/doc/logo-oss-light.svg" width='600px'>
</picture>
</a>
</p>

<div align="center">
3 changes: 3 additions & 0 deletions doc/logo-oss-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions doc/logo-oss-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionSha256Sum=7a00d51fb93147819aab76024feece20b6b84e420694101f276be952e08bef03
distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94
2 changes: 1 addition & 1 deletion rewrite-core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ dependencies {

api("org.jspecify:jspecify:latest.release")

implementation("org.apache.commons:commons-compress:latest.release")
implementation("org.apache.commons:commons-lang3:latest.release")

implementation("io.micrometer:micrometer-core:1.9.+")
implementation("io.github.classgraph:classgraph:latest.release")
Original file line number Diff line number Diff line change
@@ -15,8 +15,12 @@
*/
package org.openrewrite;

import org.jspecify.annotations.Nullable;
import org.openrewrite.scheduling.WorkingDirectoryExecutionContextView;

public class CursorValidatingExecutionContextView extends DelegatingExecutionContext {
private static final String VALIDATE_CURSOR_ACYCLIC = "org.openrewrite.CursorValidatingExecutionContextView.ValidateCursorAcyclic";
private static final String VALIDATE_CTX_MUTATION = "org.openrewrite.CursorValidatingExecutionContextView.ValidateExecutionContextImmutability";

public CursorValidatingExecutionContextView(ExecutionContext delegate) {
super(delegate);
@@ -38,4 +42,28 @@ public CursorValidatingExecutionContextView setValidateCursorAcyclic(boolean val
putMessage(VALIDATE_CURSOR_ACYCLIC, validateCursorAcyclic);
return this;
}

public CursorValidatingExecutionContextView setValidateImmutableExecutionContext(boolean allowExecutionContextMutation) {
putMessage(VALIDATE_CTX_MUTATION, allowExecutionContextMutation);
return this;
}

@Override
public void putMessage(String key, @Nullable Object value) {
boolean mutationAllowed = !getMessage(VALIDATE_CTX_MUTATION, false)
|| key.equals(VALIDATE_CURSOR_ACYCLIC)
|| key.equals(VALIDATE_CTX_MUTATION)
|| key.equals(ExecutionContext.CURRENT_CYCLE)
|| key.equals(ExecutionContext.CURRENT_RECIPE)
|| key.equals(ExecutionContext.DATA_TABLES)
|| key.equals(WorkingDirectoryExecutionContextView.WORKING_DIRECTORY_ROOT)
|| key.startsWith("org.openrewrite.maven") // MavenExecutionContextView stores metrics
|| key.startsWith("io.moderne"); // We ought to know what we're doing
assert mutationAllowed : "Recipe mutated execution context key \"" + key + "\". " +
"Recipes should not mutate the contents of the ExecutionContext as it allows mutable state to leak between " +
"recipes, opening the door for difficult to debug recipe composition errors. " +
"If you need to store state within the execution of a single recipe use Cursor messaging. " +
"If you want to pass state between recipes, use a ScanningRecipe instead.";
super.putMessage(key, value);
}
}
28 changes: 25 additions & 3 deletions rewrite-core/src/main/java/org/openrewrite/FindSourceFiles.java
Original file line number Diff line number Diff line change
@@ -23,6 +23,9 @@
import org.openrewrite.table.SourcesFiles;

import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;


@Value
@@ -31,7 +34,9 @@ public class FindSourceFiles extends Recipe {
transient SourcesFiles results = new SourcesFiles(this);

@Option(displayName = "File pattern",
description = "A glob expression representing a file path to search for (relative to the project root). Blank/null matches all.",
description = "A glob expression representing a file path to search for (relative to the project root). Blank/null matches all." +
"Multiple patterns may be specified, separated by a semicolon `;`. " +
"If multiple patterns are supplied any of the patterns matching will be interpreted as a match.",
required = false,
example = ".github/workflows/*.yml")
@Nullable
@@ -44,7 +49,7 @@ public String getDisplayName() {

@Override
public String getDescription() {
return "Find files by source path.";
return "Find files by source path. Paths are always interpreted as relative to the repository root.";
}

@Override
@@ -56,14 +61,30 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
if (tree instanceof SourceFile) {
SourceFile sourceFile = (SourceFile) tree;
Path sourcePath = sourceFile.getSourcePath();
if (StringUtils.isBlank(filePattern) || PathUtils.matchesGlob(sourcePath, normalize(filePattern))) {
if (matches(sourcePath)) {
results.insertRow(ctx, new SourcesFiles.Row(sourcePath.toString(),
tree.getClass().getSimpleName()));
return SearchResult.found(sourceFile);
}
}
return tree;
}

String @Nullable[] filePatterns;

private boolean matches(Path sourcePath) {
if (filePatterns == null) {
filePatterns = Optional.ofNullable(filePattern)
.map(it -> it.split(";"))
.map(Arrays::stream)
.orElseGet(Stream::empty)
.map(String::trim)
.filter(StringUtils::isNotEmpty)
.map(FindSourceFiles::normalize)
.toArray(String[]::new);
}
return filePatterns.length == 0 || Arrays.stream(filePatterns).anyMatch(pattern -> PathUtils.matchesGlob(sourcePath, pattern));
}
};
}

@@ -75,4 +96,5 @@ private static String normalize(String filePattern) {
}
return filePattern;
}

}
16 changes: 9 additions & 7 deletions rewrite-core/src/main/java/org/openrewrite/PathUtils.java
Original file line number Diff line number Diff line change
@@ -92,8 +92,10 @@ public static boolean matchesGlob(@Nullable Path path, @Nullable String globPatt
}
}
return false;
} else { // If eitherOrPatterns is empty and excludedPatterns is not
if (!matchesGlob(convertNegationToWildcard(globPattern), relativePath)) {
} else {
// When only a segment of a path is negated the other segments must still match
String wildcard = convertNegationToWildcard(globPattern);
if (!matchesGlob(wildcard, relativePath)) {
return false;
}
for (String excludedPattern : excludedPatterns) {
@@ -214,9 +216,9 @@ private static boolean matchesGlob(String pattern, String path) {

public static String convertNegationToWildcard(String globPattern) {
// Regular expression to match !(...)
String negationPattern = "\\!\\((.*?)\\)";
String negationPattern = "!\\((.*?)\\)";
// Replace all negation patterns with *
return globPattern.replaceAll(negationPattern, "*");
return globPattern.replaceAll(negationPattern, "**");
}

public static List<String> getExcludedPatterns(String globPattern) {
@@ -227,7 +229,7 @@ public static List<String> getExcludedPatterns(String globPattern) {
List<String> excludedPatterns = new ArrayList<>(3);

// Regular expression to match !(...)
String negationPattern = "\\!\\((.*?)\\)";
String negationPattern = "!\\((.*?)\\)";
Pattern pattern = Pattern.compile(negationPattern);
Matcher matcher = pattern.matcher(globPattern);

@@ -251,14 +253,14 @@ public static List<String> getEitherOrPatterns(String globPattern) {
List<String> eitherOrPatterns = new ArrayList<>(3);

// Regular expression to match {...}
String eitherOrPattern = "\\{(.*?)\\}";
String eitherOrPattern = "\\{(.*?)}";
Pattern pattern = Pattern.compile(eitherOrPattern);
Matcher matcher = pattern.matcher(globPattern);

// Find all possible patterns and generate patterns
while (matcher.find()) {
String eitherOrContent = matcher.group(1);
String[] options = eitherOrContent.split("\\,");
String[] options = eitherOrContent.split(",");
for (String option : options) {
eitherOrPatterns.add(globPattern.replace(matcher.group(), option));
}
19 changes: 13 additions & 6 deletions rewrite-core/src/main/java/org/openrewrite/SourceFile.java
Original file line number Diff line number Diff line change
@@ -17,14 +17,14 @@

import org.jspecify.annotations.Nullable;
import org.openrewrite.internal.StringUtils;
import org.openrewrite.style.NamedStyles;
import org.openrewrite.style.Style;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.function.Supplier;

public interface SourceFile extends Tree {

@@ -70,13 +70,20 @@ default boolean printEqualsInput(Parser.Input input, ExecutionContext ctx) {

<T extends SourceFile> T withFileAttributes(@Nullable FileAttributes fileAttributes);

default <S extends Style> @Nullable S getStyle(Class<S> style) {
return NamedStyles.merge(style, getMarkers().findAll(NamedStyles.class));
/**
* @deprecated Use {@link org.openrewrite.style.Style#from(Class, SourceFile)} instead.
*/
@Deprecated
default <S extends Style> @Nullable S getStyle(Class<S> styleClass) {
return Style.from(styleClass, this);
}

default <S extends Style> S getStyle(Class<S> style, S defaultStyle) {
S s = getStyle(style);
return s == null ? defaultStyle : s;
/**
* @deprecated Use {@link org.openrewrite.style.Style#from(Class, SourceFile, Supplier)} instead.
*/
@Deprecated
default <S extends Style> S getStyle(Class<S> styleClass, S defaultStyle) {
return Style.from(styleClass, this, () -> defaultStyle);
}

default <P> byte[] printAllAsBytes(P p) {
19 changes: 15 additions & 4 deletions rewrite-core/src/main/java/org/openrewrite/config/Environment.java
Original file line number Diff line number Diff line change
@@ -249,12 +249,23 @@ public Builder scanYamlResources() {
*/
@SuppressWarnings("unused")
public Builder scanJar(Path jar, Collection<Path> dependencies, ClassLoader classLoader) {
List<ClasspathScanningLoader> list = new ArrayList<>();
List<ClasspathScanningLoader> firstPassLoaderList = new ArrayList<>();
for (Path dep : dependencies) {
ClasspathScanningLoader classpathScanningLoader = new ClasspathScanningLoader(dep, properties, emptyList(), classLoader);
list.add(classpathScanningLoader);
firstPassLoaderList.add(new ClasspathScanningLoader(dep, properties, emptyList(), classLoader));
}
return load(new ClasspathScanningLoader(jar, properties, list, classLoader), list);

/*
* Second loader creation pass where the firstPassLoaderList is passed as the
* dependencyResourceLoaders list to ensure that we can resolve transitive
* dependencies using the loaders we just created. This is necessary because
* the first pass may have missing recipes since the full list of loaders was
* not provided.
*/
List<ClasspathScanningLoader> secondPassLoaderList = new ArrayList<>();
for (Path dep : dependencies) {
secondPassLoaderList.add(new ClasspathScanningLoader(dep, properties, firstPassLoaderList, classLoader));
}
return load(new ClasspathScanningLoader(jar, properties, secondPassLoaderList, classLoader), secondPassLoaderList);
}

@SuppressWarnings("unused")
34 changes: 34 additions & 0 deletions rewrite-core/src/main/java/org/openrewrite/format/LineBreaks.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.format;

public class LineBreaks {
public static String normalizeNewLines(String text, boolean useCrlf) {
if (!text.contains("\n")) {
return text;
}
StringBuilder normalized = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (useCrlf && c == '\n' && (i == 0 || text.charAt(i - 1) != '\r')) {
normalized.append('\r').append('\n');
} else if (useCrlf || c != '\r') {
normalized.append(c);
}
}
return normalized.toString();
}
}
Original file line number Diff line number Diff line change
@@ -261,6 +261,7 @@ public static <T> List<T> insert(@Nullable List<T> ls, @Nullable T t, int index)
/**
* For backwards compatibility; prefer {@link #map(List, Function)}.
*/
@Contract("null, _ -> null; !null, _ -> !null")
public static <T> @Nullable List<T> map(@Nullable List<T> ls, UnaryOperator<@Nullable T> map) {
return map(ls, (Function<T, T>) map);
}
Original file line number Diff line number Diff line change
@@ -96,6 +96,9 @@ public boolean matches(String str) {
}

public static boolean matches(NameCaseConvention convention, String str) {
if (str.isEmpty()) {
return false;
}
switch (convention) {
case LOWER_CAMEL:
if (!Character.isLowerCase(str.charAt(0)) && str.charAt(0) != '$') {
Original file line number Diff line number Diff line change
@@ -692,18 +692,20 @@ public static int indexOfNextNonWhitespace(int cursor, String source) {
continue;
} else if (length > cursor + 1) {
char next = source.charAt(cursor + 1);
if (current == '/' && next == '/') {
if (inMultiLineComment) {
if (current == '*' && next == '/') {
inMultiLineComment = false;
cursor++;
continue;
}
} else if (current == '/' && next == '/') {
inSingleLineComment = true;
cursor++;
continue;
} else if (current == '/' && next == '*') {
inMultiLineComment = true;
cursor++;
continue;
} else if (current == '*' && next == '/') {
inMultiLineComment = false;
cursor++;
continue;
}
}
if (!inMultiLineComment && !Character.isWhitespace(current)) {
Original file line number Diff line number Diff line change
@@ -17,10 +17,8 @@


import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.With;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import lombok.experimental.NonFinal;
import org.jspecify.annotations.Nullable;
import org.openrewrite.GitRemote;
@@ -44,6 +42,8 @@
import java.util.*;

import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import static org.openrewrite.Tree.randomId;

@Value
@@ -187,7 +187,7 @@ public GitProvenance(UUID id,
*/
public static @Nullable GitProvenance fromProjectDirectory(Path projectDir,
@Nullable BuildEnvironment environment,
GitRemote.@Nullable Parser gitRemoteParser) {
GitRemote.@Nullable Parser gitRemoteParser) {
if (gitRemoteParser == null) {
gitRemoteParser = new GitRemote.Parser();
}
@@ -371,16 +371,22 @@ private static List<Committer> getCommitters(Repository repository) {
head = headRef.getObjectId();
}

Map<String, Committer> committers = new TreeMap<>();
Map<String, String> committerName = new HashMap<>();
Map<String, NavigableMap<LocalDate, Integer>> commitMap = new HashMap<>();
for (RevCommit commit : git.log().add(head).call()) {
PersonIdent who = commit.getAuthorIdent();
Committer committer = committers.computeIfAbsent(who.getEmailAddress(),
email -> new Committer(who.getName(), email, new TreeMap<>()));
committer.getCommitsByDay().compute(who.getWhen().toInstant().atZone(who.getTimeZone().toZoneId())
committerName.putIfAbsent(who.getEmailAddress(), who.getName());
commitMap.computeIfAbsent(who.getEmailAddress(),
email -> new TreeMap<>()).compute(who.getWhen().toInstant().atZone(who.getTimeZone().toZoneId())
.toLocalDate(),
(day, count) -> count == null ? 1 : count + 1);
}
return new ArrayList<>(committers.values());
return committerName.entrySet().stream()
.map(c -> new Committer(
c.getValue(),
c.getKey(),
commitMap.get(c.getKey()))
).collect(toList());
} catch (IOException | GitAPIException e) {
return emptyList();
}
@@ -419,10 +425,47 @@ public enum EOL {
}

@Value
@With
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static class Committer {

@With
String name;

@With
String email;
NavigableMap<LocalDate, Integer> commitsByDay;

@Getter(AccessLevel.PRIVATE)
int[] data;

@JsonCreator
static Committer from(
@JsonProperty("name") String name, @JsonProperty("email") String email,
@JsonProperty("dates") int @Nullable [] data,
@Nullable @JsonProperty("commitsByDay") NavigableMap<LocalDate, Integer> commitsByDay) {
if (commitsByDay != null) {
return new Committer(name, email, commitsByDay);
} else {
return new Committer(name, email, requireNonNull(data));
}
}

public Committer(String name, String email, NavigableMap<LocalDate, Integer> commitsByDay) {
this.name = name;
this.email = email;
this.data = new int[commitsByDay.size() * 2];
int i = 0;
for (Map.Entry<LocalDate, Integer> entry : commitsByDay.entrySet()) {
data[i++] = (int) entry.getKey().toEpochDay();
data[i++] = entry.getValue();
}
}

public NavigableMap<LocalDate, Integer> getCommitsByDay() {
TreeMap<LocalDate, Integer> commitsByDay = new TreeMap<>();
for (int i = 0; i < data.length; ) {
commitsByDay.put(LocalDate.ofEpochDay(data[i++]), data[i++]);
}
return commitsByDay;
}
}
}
31 changes: 12 additions & 19 deletions rewrite-core/src/main/java/org/openrewrite/remote/Remote.java
Original file line number Diff line number Diff line change
@@ -44,10 +44,6 @@
* If a Checksum is provided it will be used to validate the integrity of the downloaded file.
*/
public interface Remote extends SourceFile {
URI getUri();

<R extends Remote> R withUri(URI uri);

/**
* Any text describing what this remote URI represents. Used to present human-readable results to an end user.
*/
@@ -66,7 +62,6 @@ default <T extends SourceFile> T withChecksum(@Nullable Checksum checksum) {
return (T) this;
}


/**
* Download the remote file
*
@@ -97,16 +92,12 @@ default <P> boolean isAcceptable(TreeVisitor<?, P> v, P p) {
return v.isAdaptableTo(RemoteVisitor.class);
}

static Builder builder(SourceFile before, URI uri) {
return new Builder(before.getId(), before.getSourcePath(), before.getMarkers(), uri);
static Builder builder(SourceFile before) {
return new Builder(before.getId(), before.getSourcePath(), before.getMarkers());
}

static Builder builder(Path sourcePath, URI uri) {
return new Builder(Tree.randomId(), sourcePath, Markers.EMPTY, uri);
}

static Builder builder(UUID id, Path sourcePath, Markers markers, URI uri) {
return new Builder(id, sourcePath, markers, uri);
static Builder builder(Path sourcePath) {
return new Builder(Tree.randomId(), sourcePath, Markers.EMPTY);
}

@Override
@@ -127,7 +118,6 @@ class Builder {
protected final UUID id;
protected final Path sourcePath;
protected final Markers markers;
protected final URI uri;

@Nullable
@Language("markdown")
@@ -144,11 +134,10 @@ class Builder {
@Nullable
FileAttributes fileAttributes;

Builder(UUID id, Path sourcePath, Markers markers, URI uri) {
Builder(UUID id, Path sourcePath, Markers markers) {
this.id = id;
this.sourcePath = sourcePath;
this.markers = markers;
this.uri = uri;
}

public Builder description(@Language("markdown") String description) {
@@ -176,17 +165,21 @@ public Builder checksum(Checksum checksum) {
return this;
}

public RemoteFile build() {
public RemoteResource build(InputStream inputStream) {
return new RemoteResource(id, sourcePath, markers, inputStream, charset, charsetBomMarked, fileAttributes, description, checksum);
}

public RemoteFile build(URI uri) {
return new RemoteFile(id, sourcePath, markers, uri, charset, charsetBomMarked, fileAttributes, description, checksum);
}

public RemoteArchive build(Path path) {
public RemoteArchive build(URI uri, Path path) {
return new RemoteArchive(id, sourcePath, markers, uri, charset, charsetBomMarked, fileAttributes, description,
Arrays.asList(path.toString().replace("/", "\\/").replace(".", "\\.")
.split("!")), checksum);
}

public RemoteArchive build(String... paths) {
public RemoteArchive build(URI uri, String... paths) {
return new RemoteArchive(id, sourcePath, markers, uri, charset, charsetBomMarked, fileAttributes, description,
Arrays.asList(paths), checksum);
}
Original file line number Diff line number Diff line change
@@ -47,8 +47,8 @@
* Useful when a Recipe wishes to create a SourceFile based on something specific from within a remote archive, but not
* the entire archive.
* <p>
* Downloading and extracting the correct file from within the archive are not handled during Recipe execution.
* Post-processing of Recipe results by a build plugin or other caller of OpenRewrite is responsible for this.
* Downloading and extracting the correct file from within the archive are not handled during recipe execution.
* Post-processing of recipe results by a build plugin or other caller of OpenRewrite is responsible for this.
*/
@Value
@EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true)
@@ -70,6 +70,7 @@ public class RemoteArchive implements Remote {
FileAttributes fileAttributes;

@Language("markdown")
@Nullable
String description;

/**
@@ -92,10 +93,10 @@ public InputStream getInputStream(ExecutionContext ctx) {
RemoteArtifactCache cache = RemoteExecutionContextView.view(ctx).getArtifactCache();
try {
Path localArchive = cache.compute(uri, () -> {
//noinspection resource
if ("file".equals(uri.getScheme())) {
return Files.newInputStream(Paths.get(uri));
}
//noinspection resource
HttpSender.Response response = httpSender.send(httpSender.get(uri.toString()).build());
if (response.isSuccessful()) {
return response.getBody();
Original file line number Diff line number Diff line change
@@ -56,6 +56,7 @@ public class RemoteFile implements Remote {
FileAttributes fileAttributes;

@Language("markdown")
@Nullable
String description;

@Nullable
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.remote;

import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.With;
import org.intellij.lang.annotations.Language;
import org.jspecify.annotations.Nullable;
import org.openrewrite.Checksum;
import org.openrewrite.ExecutionContext;
import org.openrewrite.FileAttributes;
import org.openrewrite.marker.Markers;

import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.UUID;

/**
* A remote resource that can be fetched from an {@link java.io.InputStream} which
* could be, for example, a classpath resource.
*/
@Value
@EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true)
@With
public class RemoteResource implements Remote {
@EqualsAndHashCode.Include
UUID id;

Path sourcePath;
Markers markers;
InputStream inputStream;

@Nullable
Charset charset;

boolean charsetBomMarked;

@Nullable
FileAttributes fileAttributes;

@Language("markdown")
@Nullable
String description;

@Nullable
Checksum checksum;

/**
* Note that this method can only be called once, consuming the
* {@link InputStream} in the process.
*
* @param ctx Unused in this implementation of {@link Remote}.
* @return The data of the file.
*/
@Override
public InputStream getInputStream(ExecutionContext ctx) {
return inputStream;
}
}
Original file line number Diff line number Diff line change
@@ -140,9 +140,8 @@ public int compare(@Nullable String currentVersion, String v1, String v2) {
// parts are the same:
//
// HyphenRange [25-28] should include "28-jre" and "28-android" as possible candidates.
String normalized1 = metadataPattern == null ? nv1 : nv1.replace(metadataPattern, "");
String normalized2 = metadataPattern == null ? nv2 : nv1.replace(metadataPattern, "");

String normalized1 = metadataPattern == null ? nv1 : nv1.replaceAll(metadataPattern, "");
String normalized2 = metadataPattern == null ? nv2 : nv2.replaceAll(metadataPattern, "");
try {
for (int i = 1; i <= Math.max(vp1, vp2); i++) {
String v1Part = v1Gav.group(i);
Original file line number Diff line number Diff line change
@@ -19,5 +19,11 @@

@Value
public class GeneralFormatStyle implements Style{
public static final GeneralFormatStyle DEFAULT = new GeneralFormatStyle(false);

boolean useCRLFNewLines;

public String newLine() {
return useCRLFNewLines ? "\r\n" : "\n";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.style;

public enum LineWrapSetting {
DoNotWrap, WrapAlways;
// Eventually we would add values like WrapIfTooLong or ChopIfTooLong

}
Original file line number Diff line number Diff line change
@@ -109,4 +109,10 @@ public class NamedStyles implements Marker {
public Validated<Object> validate() {
return Validated.none();
}

public <S extends Style> S getStyle(Class<S> styleClass) {
S ret = merge(styleClass, Collections.singletonList(this));
assert(ret != null);
return ret;
}
}
13 changes: 13 additions & 0 deletions rewrite-core/src/main/java/org/openrewrite/style/Style.java
Original file line number Diff line number Diff line change
@@ -19,8 +19,12 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import org.jspecify.annotations.Nullable;
import org.openrewrite.SourceFile;

import java.util.function.Function;
import java.util.function.Supplier;

/**
* Styles represent project-level standards that each source file is expected to follow, e.g.
* import ordering. They are provided to parser implementations and expected to be stored on
@@ -40,4 +44,13 @@ default Style merge(Style lowerPrecedence) {
}

default Style applyDefaults() { return this; }

static <S extends Style> @Nullable S from(Class<S> styleClass, SourceFile sf) {
return NamedStyles.merge(styleClass, sf.getMarkers().findAll(NamedStyles.class));
}

static <S extends Style> S from(Class<S> styleClass, SourceFile sf, Supplier<S> defaultStyle) {
S s = from(styleClass, sf);
return s == null ? defaultStyle.get() : s;
}
}
12 changes: 2 additions & 10 deletions rewrite-core/src/main/java/org/openrewrite/text/Find.java
Original file line number Diff line number Diff line change
@@ -28,7 +28,6 @@
import org.openrewrite.table.TextMatches;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -86,7 +85,7 @@ public String getDescription() {
description = "A glob expression that can be used to constrain which directories or source files should be searched. " +
"Multiple patterns may be specified, separated by a semicolon `;`. " +
"If multiple patterns are supplied any of the patterns matching will be interpreted as a match. " +
"When not set, all source files are searched. ",
"When not set, all source files are searched.",
required = false,
example = "**/*.java")
@Nullable
@@ -179,15 +178,8 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
return plainText.withText("").withSnippets(snippets);
}
};
//noinspection DuplicatedCode
if (filePattern != null) {
//noinspection unchecked
TreeVisitor<?, ExecutionContext> check = Preconditions.or(Arrays.stream(filePattern.split(";"))
.map(FindSourceFiles::new)
.map(Recipe::getVisitor)
.toArray(TreeVisitor[]::new));

visitor = Preconditions.check(check, visitor);
visitor = Preconditions.check(new FindSourceFiles(filePattern), visitor);
}
return visitor;
}
Original file line number Diff line number Diff line change
@@ -27,7 +27,6 @@
import org.openrewrite.quark.Quark;
import org.openrewrite.remote.Remote;

import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -95,7 +94,7 @@ public String getDescription() {
description = "A glob expression that can be used to constrain which directories or source files should be searched. " +
"Multiple patterns may be specified, separated by a semicolon `;`. " +
"If multiple patterns are supplied any of the patterns matching will be interpreted as a match. " +
"When not set, all source files are searched. ",
"When not set, all source files are searched.",
required = false,
example = "**/*.java")
@Nullable
@@ -153,15 +152,8 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
.withMarkers(sourceFile.getMarkers().add(new AlreadyReplaced(randomId(), find, replace)));
}
};
//noinspection DuplicatedCode
if (filePattern != null) {
//noinspection unchecked
TreeVisitor<?, ExecutionContext> check = Preconditions.or(Arrays.stream(filePattern.split(";"))
.map(FindSourceFiles::new)
.map(Recipe::getVisitor)
.toArray(TreeVisitor[]::new));

visitor = Preconditions.check(check, visitor);
visitor = Preconditions.check(new FindSourceFiles(filePattern), visitor);
}

if (Boolean.TRUE.equals(plaintextOnly)) {
16 changes: 15 additions & 1 deletion rewrite-core/src/main/java/org/openrewrite/text/PlainText.java
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@
*/
@Value
@Builder
@AllArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class PlainText implements SourceFileWithReferences, Tree {

@Builder.Default
@@ -82,13 +82,27 @@ public SourceFile withCharset(Charset charset) {
@Builder.Default
String text = "";

@Nullable
List<Snippet> snippets;

@Nullable
@NonFinal
@ToString.Exclude
transient SoftReference<References> references;

public PlainText(UUID id, Path sourcePath, Markers markers, @Nullable String charsetName, boolean charsetBomMarked,
@Nullable FileAttributes fileAttributes, @Nullable Checksum checksum, String text, @Nullable List<Snippet> snippets) {
this.id = id;
this.sourcePath = sourcePath;
this.markers = markers;
this.charsetName = charsetName;
this.charsetBomMarked = charsetBomMarked;
this.fileAttributes = fileAttributes;
this.checksum = checksum;
this.text = text;
this.snippets = snippets;
}

@Override
public References getReferences() {
this.references = build(this.references);
Original file line number Diff line number Diff line change
@@ -20,7 +20,6 @@
import org.openrewrite.Parser;
import org.openrewrite.SourceFile;
import org.openrewrite.internal.EncodingDetectingInputStream;
import org.openrewrite.marker.Markers;
import org.openrewrite.tree.ParseError;
import org.openrewrite.tree.ParsingEventListener;
import org.openrewrite.tree.ParsingExecutionContextView;
@@ -34,8 +33,6 @@
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static org.openrewrite.Tree.randomId;

public class PlainTextParser implements Parser {

/**
@@ -73,18 +70,13 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re
try {
EncodingDetectingInputStream is = input.getSource(ctx);
String sourceStr = is.readFully();
PlainText plainText = new PlainText(
randomId(),
path,
Markers.EMPTY,
is.getCharset().name(),
is.isCharsetBomMarked(),
input.getFileAttributes(),
null,
sourceStr,
null,
null
);
PlainText plainText = PlainText.builder()
.sourcePath(path)
.charsetName(is.getCharset().name())
.charsetBomMarked(is.isCharsetBomMarked())
.fileAttributes(input.getFileAttributes())
.text(sourceStr)
.build();
parsingListener.parsed(input, plainText);
return plainText;
} catch (Throwable t) {
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@

import org.junit.jupiter.api.Test;
import org.openrewrite.test.RewriteTest;
import org.openrewrite.test.TypeValidation;
import org.openrewrite.text.PlainText;
import org.openrewrite.text.PlainTextVisitor;

@@ -41,7 +42,8 @@ public PlainText visitText(PlainText text, ExecutionContext ctx) {
cycles.incrementAndGet();
return text;
}
}).withCausesAnotherCycle(true)),
}).withCausesAnotherCycle(true))
.typeValidationOptions(TypeValidation.all().immutableExecutionContext(false)),
text("hello world")
);
assertThat(cycles.get()).isEqualTo(2);
Original file line number Diff line number Diff line change
@@ -44,11 +44,8 @@ void gradleWrapper(String version) throws Exception {
ExecutionContext ctx = new InMemoryExecutionContext();

RemoteArchive remoteArchive = Remote
.builder(
Paths.get("gradle/wrapper/gradle-wrapper.jar"),
distributionUrl.toURI()
)
.build("gradle-[^\\/]+\\/(?:.*\\/)+gradle-wrapper-(?!shared).*\\.jar");
.builder(Paths.get("gradle/wrapper/gradle-wrapper.jar"))
.build(distributionUrl.toURI(), "gradle-[^\\/]+\\/(?:.*\\/)+gradle-wrapper-(?!shared).*\\.jar");

long actual = getInputStreamSize(remoteArchive.getInputStream(ctx));
assertThat(actual).isGreaterThan(50_000);
@@ -63,11 +60,8 @@ void gradleWrapperDownloadFails() throws Exception {
.setLargeFileHttpSender(new MockHttpSender(408));

RemoteArchive remoteArchive = Remote
.builder(
Paths.get("gradle/wrapper/gradle-wrapper.jar"),
distributionUrl.toURI()
)
.build("gradle-[^\\/]+\\/(?:.*\\/)+gradle-wrapper-(?!shared).*\\.jar");
.builder(Paths.get("gradle/wrapper/gradle-wrapper.jar"))
.build(distributionUrl.toURI(), "gradle-[^\\/]+\\/(?:.*\\/)+gradle-wrapper-(?!shared).*\\.jar");

assertThatThrownBy(() -> getInputStreamSize(remoteArchive.getInputStream(ctx)))
.isInstanceOf(IllegalStateException.class)
@@ -94,11 +88,8 @@ void gradleWrapperConcurrent(String version) throws Exception {
.setLargeFileHttpSender(new MockHttpSender(distributionUrl::openStream));

RemoteArchive remoteArchive = Remote
.builder(
Paths.get("gradle/wrapper/gradle-wrapper.jar"),
distributionUrl.toURI()
)
.build("gradle-[^\\/]+\\/(?:.*\\/)+gradle-wrapper-(?!shared).*\\.jar");
.builder(Paths.get("gradle/wrapper/gradle-wrapper.jar"))
.build(distributionUrl.toURI(), "gradle-[^\\/]+\\/(?:.*\\/)+gradle-wrapper-(?!shared).*\\.jar");

return getInputStreamSize(remoteArchive.getInputStream(ctx));
});
@@ -118,11 +109,8 @@ void printingRemoteArchive() throws URISyntaxException {
URL zipUrl = requireNonNull(RemoteArchiveTest.class.getClassLoader().getResource("zipfile.zip"));

RemoteArchive remoteArchive = Remote
.builder(
Paths.get("content.txt"),
zipUrl.toURI()
)
.build("content.txt");
.builder(Paths.get("content.txt"))
.build(zipUrl.toURI(), "content.txt");

String printed = remoteArchive.printAll(new PrintOutputCapture<>(0, PrintOutputCapture.MarkerPrinter.DEFAULT));
assertThat(printed).isEqualTo("this is a zipped file");
Original file line number Diff line number Diff line change
@@ -43,11 +43,8 @@ void gradleWrapperProperties() throws Exception {
.setLargeFileHttpSender(new MockHttpSender(distributionUrl::openStream));

RemoteFile remoteFile = Remote
.builder(
Paths.get("gradle/wrapper/gradle-wrapper.properties"),
distributionUrl.toURI()
)
.build();
.builder(Paths.get("gradle/wrapper/gradle-wrapper.properties"))
.build(distributionUrl.toURI());

long actual = getInputStreamSize(remoteFile.getInputStream(ctx));
assertThat(actual).isGreaterThan(800);
@@ -61,11 +58,8 @@ void gradleWrapperDownloadFails() throws Exception {
.setLargeFileHttpSender(new MockHttpSender(408));

RemoteArchive remoteFile = Remote
.builder(
Paths.get("gradle/wrapper/gradle-wrapper.properties"),
distributionUrl.toURI()
)
.build("gradle-[^\\/]+\\/(?:.*\\/)+gradle-wrapper-(?!shared).*\\.jar");
.builder(Paths.get("gradle/wrapper/gradle-wrapper.properties"))
.build(distributionUrl.toURI(), "gradle-[^\\/]+\\/(?:.*\\/)+gradle-wrapper-(?!shared).*\\.jar");


assertThatThrownBy(() -> getInputStreamSize(remoteFile.getInputStream(ctx)))
@@ -90,11 +84,8 @@ void gradleWrapperPropertiesConcurrent() throws Exception {
.setLargeFileHttpSender(new MockHttpSender(distributionUrl::openStream));

RemoteFile remoteFile = Remote
.builder(
Paths.get("gradle/wrapper/gradle-wrapper.properties"),
distributionUrl.toURI()
)
.build();
.builder(Paths.get("gradle/wrapper/gradle-wrapper.properties"))
.build(distributionUrl.toURI());

return getInputStreamSize(remoteFile.getInputStream(ctx));
});
7 changes: 7 additions & 0 deletions rewrite-gradle/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import nl.javadude.gradle.plugins.license.LicenseExtension

plugins {
id("org.openrewrite.build.language-library")
id("groovy")
@@ -57,6 +59,7 @@ dependencies {
testImplementation(project(":rewrite-test")) {
// because gradle-api fatjars this implementation already
exclude("ch.qos.logback", "logback-classic")
exclude("org.slf4j", "slf4j-nop")
}

testImplementation("org.openrewrite.gradle.tooling:model:$latest")
@@ -93,3 +96,7 @@ tasks.withType<Javadoc> {
"**/GradleSettings**"
)
}

configure<LicenseExtension> {
excludePatterns.add("**/gradle-wrapper/*")
}
2 changes: 2 additions & 0 deletions rewrite-gradle/src/main/groovy/RewriteGradleProject.groovy
Original file line number Diff line number Diff line change
@@ -193,6 +193,8 @@ abstract class RewriteGradleProject extends groovy.lang.Script implements Projec
abstract Task task(Map<String, ?> options)
abstract Task task(Map<String, ?> options, Closure configureClosure)
abstract Task task(String name, Closure configureClosure)

@Override
abstract Task task(String name)
abstract <T extends Task> T task(String name, Class<T> type)
abstract <T extends Task> T task(String name, Class<T> type, Object... constructorArgs)
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@

import java.util.*;

import static java.util.Collections.*;
import static java.util.Objects.requireNonNull;

@Value
@@ -171,14 +172,14 @@ private boolean usesType(SourceFile sourceFile, ExecutionContext ctx) {
return tree;
}
SourceFile sourceFile = (SourceFile) tree;
sourceFile.getMarkers().findFirst(JavaProject.class).ifPresent(javaProject ->
sourceFile.getMarkers().findFirst(JavaSourceSet.class).ifPresent(sourceSet -> {
if (usesType(sourceFile, ctx)) {
acc.usingType = true;
}
Set<String> configurations = acc.configurationsByProject.computeIfAbsent(javaProject, ignored -> new HashSet<>());
configurations.add("main".equals(sourceSet.getName()) ? "implementation" : sourceSet.getName() + "Implementation");
}));
sourceFile.getMarkers().findFirst(JavaProject.class).ifPresent(javaProject -> {
if (usesType(sourceFile, ctx)) {
acc.usingType = true;
}
Set<String> configurations = acc.configurationsByProject.computeIfAbsent(javaProject, ignored -> new HashSet<>());
sourceFile.getMarkers().findFirst(JavaSourceSet.class).ifPresent(sourceSet ->
configurations.add("main".equals(sourceSet.getName()) ? "implementation" : sourceSet.getName() + "Implementation"));
});
return tree;
}
};
@@ -216,7 +217,12 @@ public TreeVisitor<?, ExecutionContext> getVisitor(Scanned acc) {

GradleProject gp = maybeGp.get();

Set<String> resolvedConfigurations = StringUtils.isBlank(configuration) ? acc.configurationsByProject.get(jp) : new HashSet<>(Collections.singletonList(configuration));
Set<String> resolvedConfigurations = StringUtils.isBlank(configuration) ?
acc.configurationsByProject.getOrDefault(jp, new HashSet<>()) :
new HashSet<>(singletonList(configuration));
if (resolvedConfigurations.isEmpty()) {
resolvedConfigurations.add("implementation");
}
Set<String> tmpConfigurations = new HashSet<>(resolvedConfigurations);
for (String tmpConfiguration : tmpConfigurations) {
GradleDependencyConfiguration gdc = gp.getConfiguration(tmpConfiguration);
Original file line number Diff line number Diff line change
@@ -24,9 +24,9 @@
import org.openrewrite.gradle.marker.GradleProject;
import org.openrewrite.gradle.search.FindGradleProject;
import org.openrewrite.gradle.trait.GradleDependency;
import org.openrewrite.gradle.util.ChangeStringLiteral;
import org.openrewrite.gradle.util.Dependency;
import org.openrewrite.gradle.util.DependencyStringNotationConverter;
import org.openrewrite.gradle.internal.ChangeStringLiteral;
import org.openrewrite.gradle.internal.Dependency;
import org.openrewrite.gradle.internal.DependencyStringNotationConverter;
import org.openrewrite.groovy.GroovyIsoVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.ListUtils;
Original file line number Diff line number Diff line change
@@ -22,9 +22,9 @@
import org.openrewrite.gradle.marker.GradleDependencyConfiguration;
import org.openrewrite.gradle.marker.GradleProject;
import org.openrewrite.gradle.trait.GradleDependency;
import org.openrewrite.gradle.util.ChangeStringLiteral;
import org.openrewrite.gradle.util.Dependency;
import org.openrewrite.gradle.util.DependencyStringNotationConverter;
import org.openrewrite.gradle.internal.ChangeStringLiteral;
import org.openrewrite.gradle.internal.Dependency;
import org.openrewrite.gradle.internal.DependencyStringNotationConverter;
import org.openrewrite.groovy.GroovyIsoVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.ListUtils;
Original file line number Diff line number Diff line change
@@ -22,9 +22,9 @@
import org.openrewrite.gradle.marker.GradleDependencyConfiguration;
import org.openrewrite.gradle.marker.GradleProject;
import org.openrewrite.gradle.trait.GradleDependency;
import org.openrewrite.gradle.util.ChangeStringLiteral;
import org.openrewrite.gradle.util.Dependency;
import org.openrewrite.gradle.util.DependencyStringNotationConverter;
import org.openrewrite.gradle.internal.ChangeStringLiteral;
import org.openrewrite.gradle.internal.Dependency;
import org.openrewrite.gradle.internal.DependencyStringNotationConverter;
import org.openrewrite.groovy.GroovyIsoVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.ListUtils;
Original file line number Diff line number Diff line change
@@ -20,10 +20,11 @@
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.gradle.trait.GradleDependency;
import org.openrewrite.gradle.util.Dependency;
import org.openrewrite.gradle.util.DependencyStringNotationConverter;
import org.openrewrite.gradle.internal.Dependency;
import org.openrewrite.gradle.internal.DependencyStringNotationConverter;
import org.openrewrite.groovy.GroovyIsoVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.StringUtils;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
@@ -96,7 +97,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu
GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher()
.configuration(configuration);

if (!(gradleDependencyMatcher.get(getCursor()).isPresent() || dependencyDsl.matches(m))) {
if (!gradleDependencyMatcher.get(getCursor()).isPresent() && !matchesOtherDependency(m)) {
return m;
}

@@ -223,6 +224,23 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu

return m.withName(m.getName().withSimpleName(newConfiguration));
}

private boolean matchesOtherDependency(J.MethodInvocation m) {
if (!dependencyDsl.matches(m)) {
return false;
}

if (m.getArguments().isEmpty() || !(m.getArguments().get(0) instanceof J.MethodInvocation)) {
return false;
}

J.MethodInvocation inner = (J.MethodInvocation) m.getArguments().get(0);
if (!(inner.getSimpleName().equals("project") || inner.getSimpleName().equals("platform") || inner.getSimpleName().equals("enforcedPlatform"))) {
return false;
}

return StringUtils.isBlank(configuration) || configuration.equals(m.getSimpleName());
}
});
}
}
Original file line number Diff line number Diff line change
@@ -20,9 +20,9 @@
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.gradle.trait.GradleDependency;
import org.openrewrite.gradle.util.ChangeStringLiteral;
import org.openrewrite.gradle.util.Dependency;
import org.openrewrite.gradle.util.DependencyStringNotationConverter;
import org.openrewrite.gradle.internal.ChangeStringLiteral;
import org.openrewrite.gradle.internal.Dependency;
import org.openrewrite.gradle.internal.DependencyStringNotationConverter;
import org.openrewrite.groovy.GroovyIsoVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.ListUtils;
Original file line number Diff line number Diff line change
@@ -22,9 +22,9 @@
import org.openrewrite.gradle.marker.GradleDependencyConfiguration;
import org.openrewrite.gradle.marker.GradleProject;
import org.openrewrite.gradle.trait.GradleDependency;
import org.openrewrite.gradle.util.ChangeStringLiteral;
import org.openrewrite.gradle.util.Dependency;
import org.openrewrite.gradle.util.DependencyStringNotationConverter;
import org.openrewrite.gradle.internal.ChangeStringLiteral;
import org.openrewrite.gradle.internal.Dependency;
import org.openrewrite.gradle.internal.DependencyStringNotationConverter;
import org.openrewrite.groovy.GroovyIsoVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.ListUtils;
Original file line number Diff line number Diff line change
@@ -20,8 +20,8 @@
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.gradle.trait.GradleDependency;
import org.openrewrite.gradle.util.Dependency;
import org.openrewrite.gradle.util.DependencyStringNotationConverter;
import org.openrewrite.gradle.internal.Dependency;
import org.openrewrite.gradle.internal.DependencyStringNotationConverter;
import org.openrewrite.groovy.GroovyVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.ListUtils;
Original file line number Diff line number Diff line change
@@ -185,7 +185,9 @@ private List<MavenRepository> determineRepos(@Nullable String configuration) {
if (gradleSettings != null) {
return gradleSettings.getBuildscript().getMavenRepositories();
}
Objects.requireNonNull(gradleProject);
if (gradleProject == null) {
throw new IllegalStateException("Gradle project must be set to determine repositories."); // Caught by caller
}
return "classpath".equals(configuration) ?
gradleProject.getBuildscript().getMavenRepositories() :
gradleProject.getMavenRepositories();
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.gradle;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.groovy.GroovyIsoVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.internal.StringUtils;
import org.openrewrite.java.tree.J;

import java.util.concurrent.atomic.AtomicBoolean;

@Value
@EqualsAndHashCode(callSuper = false)
public class EnableDevelocityBuildCache extends Recipe {

@Override
public String getDisplayName() {
return "Enable Develocity build cache";
}

@Override
public String getDescription() {
return "Adds `buildCache` configuration to `develocity` where not yet present.";
}

@Option(displayName = "Enable remote build cache",
description = "Value for `//develocity/buildCache/remote/enabled`.",
example = "true",
required = false)
@Nullable
String remoteEnabled;

@Option(displayName = "Enable remote build cache push",
description = "Value for `//develocity/buildCache/remote/storeEnabled`.",
example = "System.getenv(\"CI\") != null",
required = false)
@Nullable
String remotePushEnabled;

@Override
public Validated<Object> validate(ExecutionContext ctx) {
return super.validate(ctx)
.and(Validated.notBlank("remoteEnabled", remoteEnabled)
.or(Validated.notBlank("remotePushEnabled", remotePushEnabled)));
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new IsSettingsGradle<>(), new GroovyIsoVisitor<ExecutionContext>() {
@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
if ("develocity".equals(method.getSimpleName()) && !hasBuildCache(method)) {
J.MethodInvocation buildCache = createBuildCache();
return maybeAutoFormat(method, method.withArguments(ListUtils.mapFirst(method.getArguments(), arg -> {
if (arg instanceof J.Lambda) {
J.Lambda lambda = (J.Lambda) arg;
J.Block block = (J.Block) lambda.getBody();
return lambda.withBody(block.withStatements(ListUtils.concat(block.getStatements(), buildCache)));
}
return arg;
})), ctx);
}
return method;
}

private boolean hasBuildCache(J.MethodInvocation m) {
return new GroovyIsoVisitor<AtomicBoolean>() {
@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, AtomicBoolean atomicBoolean) {
if ("buildCache".equals(method.getSimpleName())) {
atomicBoolean.set(true);
return method;
}
return super.visitMethodInvocation(method, atomicBoolean);
}
}.reduce(m, new AtomicBoolean(false), getCursor().getParentTreeCursor()).get();
}
});
}

private J.MethodInvocation createBuildCache() {
String conf = "buildCache {\n" +
" remote(develocity.buildCache) {\n";
if (!StringUtils.isBlank(remoteEnabled)) {
conf += " enabled = " + remoteEnabled + "\n";
}
if (!StringUtils.isBlank(remotePushEnabled)) {
conf += " push = " + remotePushEnabled + "\n";
}
conf += " }" +
"}";
return (J.MethodInvocation) GradleParser.builder().build()
.parse(conf)
.map(G.CompilationUnit.class::cast)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Could not parse as Gradle"))
.getStatements()
.get(0);
}
}
Original file line number Diff line number Diff line change
@@ -22,9 +22,9 @@
import org.openrewrite.gradle.marker.GradleDependencyConfiguration;
import org.openrewrite.gradle.marker.GradleProject;
import org.openrewrite.gradle.trait.GradleDependency;
import org.openrewrite.gradle.util.ChangeStringLiteral;
import org.openrewrite.gradle.util.Dependency;
import org.openrewrite.gradle.util.DependencyStringNotationConverter;
import org.openrewrite.gradle.internal.ChangeStringLiteral;
import org.openrewrite.gradle.internal.Dependency;
import org.openrewrite.gradle.internal.DependencyStringNotationConverter;
import org.openrewrite.groovy.GroovyIsoVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.ListUtils;
@@ -33,6 +33,7 @@
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeUtils;
import org.openrewrite.maven.MavenDownloadingException;
import org.openrewrite.maven.internal.MavenPomDownloader;
import org.openrewrite.maven.tree.GroupArtifactVersion;
@@ -44,38 +45,42 @@
import org.openrewrite.semver.VersionComparator;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Objects.requireNonNull;

@Value
@EqualsAndHashCode(callSuper = false)
public class RemoveRedundantDependencyVersions extends Recipe {
@Option(displayName = "Group",
description = "Group glob expression pattern used to match dependencies that should be managed." +
"Group is the first part of a dependency coordinate `com.google.guava:guava:VERSION`.",
"Group is the first part of a dependency coordinate `com.google.guava:guava:VERSION`.",
example = "com.google.*",
required = false)
@Nullable
String groupPattern;

@Option(displayName = "Artifact",
description = "Artifact glob expression pattern used to match dependencies that should be managed." +
"Artifact is the second part of a dependency coordinate `com.google.guava:guava:VERSION`.",
"Artifact is the second part of a dependency coordinate `com.google.guava:guava:VERSION`.",
example = "guava*",
required = false)
@Nullable
String artifactPattern;

@Option(displayName = "Only if managed version is ...",
description = "Only remove the explicit version if the managed version has the specified comparative relationship to the explicit version. " +
"For example, `gte` will only remove the explicit version if the managed version is the same or newer. " +
"Default `eq`.",
"For example, `gte` will only remove the explicit version if the managed version is the same or newer. " +
"Default `eq`.",
valid = {"any", "eq", "lt", "lte", "gt", "gte"},
required = false)
@Nullable
Comparator onlyIfManagedVersionIs;

@Option(displayName = "Except",
description = "Accepts a list of GAVs. Dependencies matching a GAV will be ignored by this recipe."
+ " GAV versions are ignored if provided.",
+ " GAV versions are ignored if provided.",
example = "com.jcraft:jsch",
required = false)
@Nullable
@@ -100,6 +105,10 @@ public String getDescription() {
return "Remove explicitly-specified dependency versions that are managed by a Gradle `platform`/`enforcedPlatform`.";
}

private static final MethodMatcher CONSTRAINTS_MATCHER = new MethodMatcher("DependencyHandlerSpec constraints(..)");
private static final MethodMatcher INDIVIDUAL_CONSTRAINTS_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)");
private static final VersionComparator VERSION_COMPARATOR = requireNonNull(Semver.validate("latest.release", null).getValue());

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
@@ -192,6 +201,80 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu
return m;
}
}.visit(cu, ctx);
cu = (G.CompilationUnit) new GroovyIsoVisitor<ExecutionContext>() {

@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
if (CONSTRAINTS_MATCHER.matches(m)) {
if (m.getArguments().isEmpty() || !(m.getArguments().get(0) instanceof J.Lambda)) {
return m;
}
J.Lambda l = (J.Lambda) m.getArguments().get(0);
if (!(l.getBody() instanceof J.Block)) {
return m;
}
J.Block b = (J.Block) l.getBody();
if (b.getStatements().isEmpty()) {
//noinspection DataFlowIssue
return null;
}
return m;
} else if (INDIVIDUAL_CONSTRAINTS_MATCHER.matches(m)) {
if (!TypeUtils.isAssignableTo("org.gradle.api.artifacts.Dependency", requireNonNull(m.getMethodType()).getReturnType())) {
return m;
}
if (m.getArguments().isEmpty() || !(m.getArguments().get(0) instanceof J.Literal) || !(((J.Literal) m.getArguments().get(0)).getValue() instanceof String)) {
return m;
}
//noinspection DataFlowIssue
if (shouldRemoveRedundantConstraint((String) ((J.Literal) m.getArguments().get(0)).getValue(), m.getSimpleName())) {
//noinspection DataFlowIssue
return null;
}
}
return m;
}

@Override
public J.Return visitReturn(J.Return _return, ExecutionContext ctx) {
J.Return r = super.visitReturn(_return, ctx);
if (r.getExpression() == null) {
//noinspection DataFlowIssue
return null;
}
return r;
}

boolean shouldRemoveRedundantConstraint(String dependencyNotation, String configurationName) {
return shouldRemoveRedundantConstraint(
DependencyStringNotationConverter.parse(dependencyNotation),
gp.getConfiguration(configurationName));
}

boolean shouldRemoveRedundantConstraint(@Nullable Dependency constraint, @Nullable GradleDependencyConfiguration c) {
if (c == null || constraint == null || constraint.getVersion() == null) {
return false;
}
if(constraint.getVersion().contains("[") || constraint.getVersion().contains("!!")) {
// https://docs.gradle.org/current/userguide/dependency_versions.html#sec:strict-version
return false;
}
if ((groupPattern != null && !StringUtils.matchesGlob(constraint.getGroupId(), groupPattern))
|| (artifactPattern != null && !StringUtils.matchesGlob(constraint.getArtifactId(), artifactPattern))) {
return false;
}
return Stream.concat(
Stream.of(c),
gp.configurationsExtendingFrom(c, true).stream()
)
.filter(GradleDependencyConfiguration::isCanBeResolved)
.distinct()
.map(conf -> conf.findResolvedDependency(requireNonNull(constraint.getGroupId()), constraint.getArtifactId()))
.filter(Objects::nonNull)
.anyMatch(resolvedDependency -> VERSION_COMPARATOR.compare(null, resolvedDependency.getVersion(), constraint.getVersion()) > 0);
}
}.visitNonNull(cu, ctx);

return super.visitCompilationUnit(cu, ctx);
}
@@ -258,7 +341,7 @@ private J.MethodInvocation maybeRemoveVersion(J.MethodInvocation m) {
G.MapLiteral mapLiteral = (G.MapLiteral) arg;
return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), entry -> {
if (entry.getKey() instanceof J.Literal &&
"version".equals(((J.Literal) entry.getKey()).getValue())) {
"version".equals(((J.Literal) entry.getKey()).getValue())) {
return null;
}
return entry;
@@ -268,7 +351,7 @@ private J.MethodInvocation maybeRemoveVersion(J.MethodInvocation m) {
return m.withArguments(ListUtils.map(m.getArguments(), arg -> {
G.MapEntry entry = (G.MapEntry) arg;
if (entry.getKey() instanceof J.Literal &&
"version".equals(((J.Literal) entry.getKey()).getValue())) {
"version".equals(((J.Literal) entry.getKey()).getValue())) {
return null;
}
return entry;
Original file line number Diff line number Diff line change
@@ -222,22 +222,24 @@ public Properties visitEntry(Properties.Entry entry, ExecutionContext ctx) {
// Typical example: https://services.gradle.org/distributions/gradle-7.4-all.zip or https://company.com/repo/gradle-8.2-bin.zip
String currentDistributionUrl = entry.getValue().getText();

GradleWrapper gradleWrpr = getGradleWrapper(ctx);
if (StringUtils.isBlank(gradleWrpr.getDistributionUrl()) && !StringUtils.isBlank(version) &&
if (gradleWrapper == null) {
getGradleWrapper(ctx);
}
if (StringUtils.isBlank(gradleWrapper.getDistributionUrl()) && !StringUtils.isBlank(version) &&
Semver.validate(version, null).getValue() instanceof ExactVersion) {
String newDownloadUrl = currentDistributionUrl.replace("\\", "")
.replaceAll("(.*gradle-)(\\d+\\.\\d+(?:\\.\\d+)?)(.*-(?:bin|all).zip)",
"$1" + gradleWrapper.getVersion() + "$3");
gradleWrapper = new GradleWrapper(version, new DistributionInfos(newDownloadUrl, null, null));
}
String wrapperHost = currentDistributionUrl.substring(0, currentDistributionUrl.lastIndexOf("/")) + "/gradle-";
if (StringUtils.isBlank(wrapperUri) && !StringUtils.isBlank(gradleWrpr.getDistributionUrl()) &&
!gradleWrpr.getPropertiesFormattedUrl().startsWith(wrapperHost)) {
String newDownloadUrl = gradleWrpr.getDistributionUrl()
if (StringUtils.isBlank(wrapperUri) && !StringUtils.isBlank(gradleWrapper.getDistributionUrl()) &&
!gradleWrapper.getPropertiesFormattedUrl().startsWith(wrapperHost)) {
String newDownloadUrl = gradleWrapper.getDistributionUrl()
.replace("\\", "")
.replaceAll("(.*gradle-)(\\d+\\.\\d+(?:\\.\\d+)?)(.*-(?:bin|all).zip)",
wrapperHost + gradleWrapper.getVersion() + "$3");
gradleWrapper = new GradleWrapper(gradleWrpr.getVersion(), new DistributionInfos(newDownloadUrl, null, null));
gradleWrapper = new GradleWrapper(gradleWrapper.getVersion(), new DistributionInfos(newDownloadUrl, null, null));
}

if (!gradleWrapper.getPropertiesFormattedUrl().equals(currentDistributionUrl)) {
@@ -359,6 +361,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor(GradleWrapperState acc) {
@Override
public Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
if (!(tree instanceof SourceFile)) {
//noinspection DataFlowIssue
return tree;
}

Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.gradle.util.ChangeStringLiteral;
import org.openrewrite.gradle.internal.ChangeStringLiteral;
import org.openrewrite.groovy.GroovyVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.ListUtils;
Original file line number Diff line number Diff line change
@@ -24,9 +24,9 @@
import org.openrewrite.gradle.marker.GradleDependencyConfiguration;
import org.openrewrite.gradle.marker.GradleProject;
import org.openrewrite.gradle.trait.GradleDependency;
import org.openrewrite.gradle.util.ChangeStringLiteral;
import org.openrewrite.gradle.util.Dependency;
import org.openrewrite.gradle.util.DependencyStringNotationConverter;
import org.openrewrite.gradle.internal.ChangeStringLiteral;
import org.openrewrite.gradle.internal.Dependency;
import org.openrewrite.gradle.internal.DependencyStringNotationConverter;
import org.openrewrite.groovy.GroovyIsoVisitor;
import org.openrewrite.groovy.GroovyVisitor;
import org.openrewrite.groovy.tree.G;
@@ -142,6 +142,7 @@ public DependencyVersionState getInitialValue(ExecutionContext ctx) {
@Override
public TreeVisitor<?, ExecutionContext> getScanner(DependencyVersionState acc) {

//noinspection BooleanMethodIsAlwaysInverted
return new GroovyVisitor<ExecutionContext>() {
@Nullable
GradleProject gradleProject;
@@ -162,9 +163,9 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx)

if (gradleDependencyMatcher.get(getCursor()).isPresent()) {
if (m.getArguments().get(0) instanceof G.MapEntry) {
String groupId = null;
String artifactId = null;
String version = null;
String declaredGroupId = null;
String declaredArtifactId = null;
String declaredVersion = null;

for (Expression e : m.getArguments()) {
if (!(e instanceof G.MapEntry)) {
@@ -200,28 +201,28 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx)
String keyValue = (String) key.getValue();
switch (keyValue) {
case "group":
groupId = valueValue;
declaredGroupId = valueValue;
break;
case "name":
artifactId = valueValue;
declaredArtifactId = valueValue;
break;
case "version":
version = valueValue;
declaredVersion = valueValue;
break;
}
}
if (groupId == null || artifactId == null || version == null) {
if (declaredGroupId == null || declaredArtifactId == null || declaredVersion == null) {
return m;
}

String versionVariableName = version;
GroupArtifact ga = new GroupArtifact(groupId, artifactId);
if (acc.gaToNewVersion.containsKey(ga)) {
String versionVariableName = declaredVersion;
GroupArtifact ga = new GroupArtifact(declaredGroupId, declaredArtifactId);
if (acc.gaToNewVersion.containsKey(ga) || !shouldResolveVersion(declaredGroupId, declaredArtifactId)) {
return m;
}
try {
String resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null)
.select(new GroupArtifact(groupId, artifactId), m.getSimpleName(), newVersion, versionPattern, ctx);
.select(new GroupArtifact(declaredGroupId, declaredArtifactId), m.getSimpleName(), newVersion, versionPattern, ctx);
acc.versionPropNameToGA.put(versionVariableName, ga);
// It is fine for this value to be null, record it in the map to avoid future lookups
//noinspection DataFlowIssue
@@ -249,7 +250,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx)
}
String versionVariableName = ((J.Identifier) versionValue.getTree()).getSimpleName();
GroupArtifact ga = new GroupArtifact(dep.getGroupId(), dep.getArtifactId());
if (acc.gaToNewVersion.containsKey(ga)) {
if (acc.gaToNewVersion.containsKey(ga) || !shouldResolveVersion(dep.getGroupId(), dep.getArtifactId())) {
continue;
}
try {
@@ -268,6 +269,16 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx)
}
return m;
}

// Some recipes make use of UpgradeDependencyVersion as an implementation detail.
// Those other recipes might not know up-front which dependency needs upgrading
// So they use the UpgradeDependencyVersion recipe with null groupId and artifactId to pre-populate all data they could possibly need
// This works around the lack of proper recipe pipelining which might allow us to have multiple scanning phases as necessary
private boolean shouldResolveVersion(String declaredGroupId, String declaredArtifactId) {
//noinspection ConstantValue
return (groupId == null || artifactId == null) ||
new DependencyMatcher(groupId, artifactId, null).matches(declaredGroupId, declaredArtifactId);
}
};
}

Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.gradle.util;
package org.openrewrite.gradle.internal;

import org.openrewrite.Incubating;
import org.openrewrite.java.tree.J;
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.gradle.util;
package org.openrewrite.gradle.internal;

import lombok.EqualsAndHashCode;
import lombok.Value;
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.gradle.util;
package org.openrewrite.gradle.internal;

import org.jspecify.annotations.Nullable;

Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.gradle.internal;

import org.openrewrite.InMemoryExecutionContext;
import org.openrewrite.gradle.util.DistributionInfos;
import org.openrewrite.gradle.util.GradleWrapper;
import org.openrewrite.internal.StringUtils;
import org.openrewrite.remote.Remote;
import org.openrewrite.semver.LatestRelease;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.zip.Adler32;

import static org.openrewrite.gradle.util.GradleWrapper.WRAPPER_BATCH_LOCATION;
import static org.openrewrite.gradle.util.GradleWrapper.WRAPPER_SCRIPT_LOCATION;

public class GradleWrapperScriptDownloader {
private static final Path WRAPPER_SCRIPTS = Paths.get("rewrite-gradle/src/main/resources/META-INF/rewrite/gradle-wrapper/");

public static void main(String[] args) throws IOException, InterruptedException {
Lock lock = new ReentrantLock();
InMemoryExecutionContext ctx = new InMemoryExecutionContext();
List<GradleWrapper.GradleVersion> allGradleReleases = GradleWrapper.listAllVersions(null, ctx);
Map<String, GradleWrapperScriptLoader.Version> allDownloadedVersions =
new ConcurrentHashMap<>(new GradleWrapperScriptLoader().getAllVersions());

AtomicInteger i = new AtomicInteger();
ForkJoinPool pool = ForkJoinPool.commonPool();
pool.invokeAll(allGradleReleases.stream().map(version -> (Callable<Void>) () -> {
String v = version.getVersion();
String threadName = " [" + Thread.currentThread().getName() + "]";

if (allDownloadedVersions.containsKey(v)) {
System.out.printf("%03d: %s already exists. Skipping.%s%n", i.incrementAndGet(),
v, threadName);
return null;
}

try {
DistributionInfos infos = DistributionInfos.fetch(GradleWrapper.DistributionType.Bin, version, ctx);
GradleWrapper wrapper = new GradleWrapper(v, infos);

String gradlewChecksum = downloadScript(WRAPPER_SCRIPT_LOCATION, wrapper, "unix", ctx);
String gradlewBatChecksum = downloadScript(WRAPPER_BATCH_LOCATION, wrapper, "windows", ctx);

lock.lock();
allDownloadedVersions.put(v, new GradleWrapperScriptLoader.Version(v, gradlewChecksum, gradlewBatChecksum));

List<String> sortedVersions = new ArrayList<>(allDownloadedVersions.keySet());
sortedVersions.sort(new LatestRelease(null).reversed());
try (BufferedWriter writer = Files.newBufferedWriter(WRAPPER_SCRIPTS.resolve("versions.csv"))) {
writer.write("version,gradlew,gradlewBat\n");
for (String sortedVersion : sortedVersions) {
GradleWrapperScriptLoader.Version version1 = allDownloadedVersions.get(sortedVersion);
writer.write(sortedVersion + "," + version1.getGradlewChecksum() + "," + version1.getGradlewBatChecksum() + "\n");
}
}
System.out.printf("%03d: %s downloaded.%s%n", i.incrementAndGet(), v, threadName);
} catch (Throwable t) {
// FIXME There are some wrappers that are not downloading successfully. Why?
System.out.printf("%03d: %s failed to download: %s.%s%n", i.incrementAndGet(), v, t.getMessage(), threadName);
return null;
} finally {
lock.unlock();
}
return null;
}).collect(Collectors.toList()));

while (pool.getActiveThreadCount() > 0) {
//noinspection BusyWait
Thread.sleep(100);
}
}

private static String downloadScript(Path wrapperScriptLocation, GradleWrapper wrapper, String os,
InMemoryExecutionContext ctx) throws IOException, NoSuchAlgorithmException {
InputStream is = Remote.builder(wrapperScriptLocation)
.build(
URI.create(wrapper.getDistributionInfos().getDownloadUrl()),
"gradle-[^\\/]+/(?:.*\\/)+gradle-plugins-.*\\.jar",
"org/gradle/api/internal/plugins/" + os + "StartScript.txt"
)
.getInputStream(ctx);

byte[] scriptText = StringUtils.readFully(is).getBytes(StandardCharsets.UTF_8);
Adler32 adler32 = new Adler32();
adler32.update(scriptText, 0, scriptText.length);

String scriptChecksum = Long.toHexString(adler32.getValue());
Path scriptChecksumPath = WRAPPER_SCRIPTS.resolve(os).resolve(scriptChecksum + ".txt");
if (!Files.exists(scriptChecksumPath)) {
Files.write(scriptChecksumPath, scriptText);
}

return scriptChecksum;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.gradle.internal;

import lombok.Getter;
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.gradle.util.GradleWrapper;
import org.openrewrite.marker.Markup;
import org.openrewrite.remote.Remote;
import org.openrewrite.remote.RemoteResource;
import org.openrewrite.semver.LatestRelease;

import java.io.*;
import java.util.NavigableMap;
import java.util.TreeMap;

import static java.util.Objects.requireNonNull;

public class GradleWrapperScriptLoader {
@Getter
private final NavigableMap<String, Version> allVersions = new TreeMap<>(
new LatestRelease(null));

public GradleWrapperScriptLoader() {
try (BufferedReader in = new BufferedReader(new InputStreamReader(requireNonNull(
getClass().getResourceAsStream("/META-INF/rewrite/gradle-wrapper/versions.csv"))))) {
in.readLine(); // header
String line;
while ((line = in.readLine()) != null) {
String[] row = line.split(",");
allVersions.put(row[0], new Version(row[0], row[1], row[2]));
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

/**
* When the requested version is unavailable we pick the nearest available.
*/
public Nearest findNearest(@Nullable String requestedVersion) {
if (requestedVersion == null) {
return new Nearest(null, allVersions.lastEntry().getValue());
}
return new Nearest(requestedVersion, allVersions.floorEntry(requestedVersion).getValue());
}

@SuppressWarnings("resource")
@Value
public static class Nearest {
@Nullable
String requestedVersion;

Version resolved;

public RemoteResource gradlew() {
InputStream script = getClass().getResourceAsStream("/META-INF/rewrite/gradle-wrapper/unix/" + resolved.getGradlewChecksum() + ".txt");
return maybeWarn(Remote.builder(GradleWrapper.WRAPPER_SCRIPT_LOCATION)
.description(String.format("Unix Gradle wrapper script template for %s", resolved.getVersion()))
.build(requireNonNull(script)));
}

public RemoteResource gradlewBat() {
InputStream script = getClass().getResourceAsStream("/META-INF/rewrite/gradle-wrapper/windows/" + resolved.getGradlewBatChecksum() + ".txt");
return maybeWarn(Remote.builder(GradleWrapper.WRAPPER_BATCH_LOCATION)
.description(String.format("Windows Gradle wrapper script template for %s", resolved.getVersion()))
.build(requireNonNull(script)));
}

public RemoteResource maybeWarn(RemoteResource script) {
if (!resolved.getVersion().equals(requestedVersion)) {
return Markup.warn(script, new Exception(
"rewrite-gradle does not contain a script for requested version" +
(requestedVersion == null ? "" : requestedVersion + ". ") +
"Using the script from nearest available version " + resolved.getVersion() + " instead."));
}
return script;
}
}

@Value
public static class Version {
String version;
String gradlewChecksum;
String gradlewBatChecksum;
}
}
Original file line number Diff line number Diff line change
@@ -16,8 +16,6 @@
package org.openrewrite.gradle.internal;

import lombok.Getter;
import org.openrewrite.gradle.util.Dependency;
import org.openrewrite.gradle.util.DependencyStringNotationConverter;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
*/
@NullMarked
package org.openrewrite.gradle.internal;

import org.jspecify.annotations.NullMarked;
Original file line number Diff line number Diff line change
@@ -55,6 +55,15 @@ public class AddBuildPlugin extends Recipe {
@Nullable
Boolean apply;

@Option(displayName = "Accept transitive",
description = "Some plugins apply other plugins. When this is set to true no plugin declaration will be added if the plugin is already applied transitively. " +
"When this is set to false the plugin will be added explicitly even if it is already applied transitively. " +
"Defaults to `true`.",
valid = {"true", "false"},
required = false)
@Nullable
Boolean acceptTransitive;

@Override
public String getDisplayName() {
return "Add Gradle plugin";
@@ -78,7 +87,7 @@ public Validated<Object> validate() {
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new FindGradleProject(FindGradleProject.SearchCriteria.Marker),
new AddPluginVisitor(pluginId, version, versionPattern, apply)
new AddPluginVisitor(pluginId, version, versionPattern, apply, acceptTransitive)
);
}
}
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@
import org.openrewrite.maven.tree.GroupArtifact;
import org.openrewrite.semver.Semver;
import org.openrewrite.semver.VersionComparator;
import org.openrewrite.style.Style;

import java.nio.file.Paths;
import java.util.Optional;
@@ -92,7 +93,7 @@ public class AddDevelocityGradlePlugin extends Recipe {
@Nullable
Boolean uploadInBackground;

@Option(displayName = "Publish Criteria",
@Option(displayName = "Publish criteria",
description = "When set to `Always` the plugin will publish build scans of every single build. " +
"When set to `Failure` the plugin will only publish build scans when the build fails. " +
"When omitted scans will be published only when the `--scan` option is passed to the build.",
@@ -210,7 +211,7 @@ public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionCon
}

private G.CompilationUnit withPlugin(G.CompilationUnit cu, String pluginId, String newVersion, VersionComparator versionComparator, ExecutionContext ctx) {
cu = (G.CompilationUnit) new AddPluginVisitor(pluginId, newVersion, null, null)
cu = (G.CompilationUnit) new AddPluginVisitor(pluginId, newVersion, null, null, false)
.visitNonNull(cu, ctx);
cu = (G.CompilationUnit) new UpgradePluginVersion(pluginId, newVersion, null).getVisitor()
.visitNonNull(cu, ctx);
@@ -314,7 +315,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Atomi
}

private static String getIndent(G.CompilationUnit cu) {
TabsAndIndentsStyle style = cu.getStyle(TabsAndIndentsStyle.class, IntelliJ.tabsAndIndents());
TabsAndIndentsStyle style = Style.from(TabsAndIndentsStyle.class, cu, IntelliJ::tabsAndIndents);
if (style.getUseTabCharacter()) {
return "\t";
} else {
Original file line number Diff line number Diff line change
@@ -39,6 +39,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import static java.util.Collections.singletonList;
@@ -58,6 +59,9 @@ public class AddPluginVisitor extends GroovyIsoVisitor<ExecutionContext> {
@Nullable
Boolean apply;

@Nullable
Boolean acceptTransitive;

private static @Nullable Comment getLicenseHeader(G.CompilationUnit cu) {
if (!cu.getStatements().isEmpty()) {
Statement firstStatement = cu.getStatements().get(0);
@@ -95,124 +99,133 @@ private static G.CompilationUnit removeLicenseHeader(G.CompilationUnit cu) {

@Override
public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) {
if (FindPlugins.find(cu, pluginId).isEmpty()) {
String version;
if (newVersion == null) {
// We have been requested to add a versionless plugin
version = null;
} else {
Optional<GradleProject> maybeGp = cu.getMarkers().findFirst(GradleProject.class);
Optional<GradleSettings> maybeGs = cu.getMarkers().findFirst(GradleSettings.class);
if (!maybeGp.isPresent() && !maybeGs.isPresent()) {
return cu;
}
if (!FindPlugins.find(cu, pluginId).isEmpty() && (acceptTransitive == null || acceptTransitive)) {
return cu;
}

try {
version = new DependencyVersionSelector(null, maybeGp.orElse(null), maybeGs.orElse(null)).select(new GroupArtifact(pluginId, pluginId + ".gradle.plugin"), "classpath", newVersion, versionPattern, ctx);
} catch (MavenDownloadingException e) {
return e.warn(cu);
}
String version;
if (newVersion == null) {
// We have been requested to add a versionless plugin
version = null;
} else {
Optional<GradleProject> maybeGp = cu.getMarkers().findFirst(GradleProject.class);
Optional<GradleSettings> maybeGs = cu.getMarkers().findFirst(GradleSettings.class);
if (!maybeGp.isPresent() && !maybeGs.isPresent()) {
return cu;
}

AtomicInteger singleQuote = new AtomicInteger();
AtomicInteger doubleQuote = new AtomicInteger();
new GroovyIsoVisitor<Integer>() {
final MethodMatcher pluginIdMatcher = new MethodMatcher("PluginSpec id(..)");

@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integer integer) {
J.MethodInvocation m = super.visitMethodInvocation(method, integer);
if (pluginIdMatcher.matches(m)) {
if (m.getArguments().get(0) instanceof J.Literal) {
J.Literal l = (J.Literal) m.getArguments().get(0);
assert l.getValueSource() != null;
if (l.getValueSource().startsWith("'")) {
singleQuote.incrementAndGet();
} else {
doubleQuote.incrementAndGet();
}
try {
version = new DependencyVersionSelector(null, maybeGp.orElse(null), maybeGs.orElse(null)).select(new GroupArtifact(pluginId, pluginId + ".gradle.plugin"), "classpath", newVersion, versionPattern, ctx);
} catch (MavenDownloadingException e) {
return e.warn(cu);
}
}

AtomicInteger singleQuote = new AtomicInteger();
AtomicInteger doubleQuote = new AtomicInteger();
AtomicBoolean pluginAlreadyApplied = new AtomicBoolean();
new GroovyIsoVisitor<Integer>() {
final MethodMatcher pluginIdMatcher = new MethodMatcher("PluginSpec id(..)");

@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integer integer) {
J.MethodInvocation m = super.visitMethodInvocation(method, integer);
if (pluginIdMatcher.matches(m)) {
if (m.getArguments().get(0) instanceof J.Literal) {
J.Literal l = (J.Literal) m.getArguments().get(0);
if (pluginId.equals(l.getValue())) {
pluginAlreadyApplied.set(true);
}
assert l.getValueSource() != null;
if (l.getValueSource().startsWith("'")) {
singleQuote.incrementAndGet();
} else {
doubleQuote.incrementAndGet();
}
}
return m;
}
}.visitCompilationUnit(cu, 0);

String delimiter = singleQuote.get() < doubleQuote.get() ? "\"" : "'";
String source = "plugins {\n" +
" id " + delimiter + pluginId + delimiter + (version != null ? " version " + delimiter + version + delimiter : "") + (version != null && Boolean.FALSE.equals(apply) ? " apply " + apply : "") + "\n" +
"}";
Statement statement = GradleParser.builder().build()
.parseInputs(singletonList(Parser.Input.fromString(source)), null, ctx)
.findFirst()
.map(parsed -> {
if (parsed instanceof ParseError) {
throw ((ParseError) parsed).toException();
}
return ((G.CompilationUnit) parsed);
})
.map(parsed -> parsed.getStatements().get(0))
.orElseThrow(() -> new IllegalArgumentException("Could not parse as Gradle"));

if (FindMethods.find(cu, "RewriteGradleProject plugins(..)").isEmpty() && FindMethods.find(cu, "RewriteSettings plugins(..)").isEmpty()) {
if (cu.getSourcePath().endsWith(Paths.get("settings.gradle")) &&
!cu.getStatements().isEmpty() &&
cu.getStatements().get(0) instanceof J.MethodInvocation &&
((J.MethodInvocation) cu.getStatements().get(0)).getSimpleName().equals("pluginManagement")) {
return cu.withStatements(ListUtils.insert(cu.getStatements(), autoFormat(statement.withPrefix(Space.format("\n\n")), ctx, getCursor()), 1));
} else {
int insertAtIdx = 0;
for (int i = 0; i < cu.getStatements().size(); i++) {
Statement existingStatement = cu.getStatements().get(i);
if (existingStatement instanceof J.MethodInvocation && ((J.MethodInvocation) existingStatement).getSimpleName().equals("buildscript")) {
insertAtIdx = i + 1;
break;
}
return m;
}
}.visitCompilationUnit(cu, 0);

if (pluginAlreadyApplied.get()) {
return cu;
}

String delimiter = singleQuote.get() < doubleQuote.get() ? "\"" : "'";
String source = "plugins {\n" +
" id " + delimiter + pluginId + delimiter + (version != null ? " version " + delimiter + version + delimiter : "") + (version != null && Boolean.FALSE.equals(apply) ? " apply " + apply : "") + "\n" +
"}";
Statement statement = GradleParser.builder().build()
.parseInputs(singletonList(Parser.Input.fromString(source)), null, ctx)
.findFirst()
.map(parsed -> {
if (parsed instanceof ParseError) {
throw ((ParseError) parsed).toException();
}
if (insertAtIdx == 0) {
Comment licenseHeader = getLicenseHeader(cu);
if (licenseHeader != null) {
cu = removeLicenseHeader(cu);
statement = statement.withComments(Collections.singletonList(licenseHeader));
}
Space leadingSpace = Space.firstPrefix(cu.getStatements());
return cu.withStatements(ListUtils.insert(
Space.formatFirstPrefix(cu.getStatements(), leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace())),
autoFormat(statement, ctx, getCursor()),
insertAtIdx));
} else {
return cu.withStatements(ListUtils.insert(cu.getStatements(), autoFormat(statement.withPrefix(Space.format("\n\n")), ctx, getCursor()), insertAtIdx));
return ((G.CompilationUnit) parsed);
})
.map(parsed -> parsed.getStatements().get(0))
.orElseThrow(() -> new IllegalArgumentException("Could not parse as Gradle"));

if (FindMethods.find(cu, "RewriteGradleProject plugins(..)").isEmpty() && FindMethods.find(cu, "RewriteSettings plugins(..)").isEmpty()) {
if (cu.getSourcePath().endsWith(Paths.get("settings.gradle")) &&
!cu.getStatements().isEmpty() &&
cu.getStatements().get(0) instanceof J.MethodInvocation &&
((J.MethodInvocation) cu.getStatements().get(0)).getSimpleName().equals("pluginManagement")) {
return cu.withStatements(ListUtils.insert(cu.getStatements(), autoFormat(statement.withPrefix(Space.format("\n\n")), ctx, getCursor()), 1));
} else {
int insertAtIdx = 0;
for (int i = 0; i < cu.getStatements().size(); i++) {
Statement existingStatement = cu.getStatements().get(i);
if (existingStatement instanceof J.MethodInvocation && ((J.MethodInvocation) existingStatement).getSimpleName().equals("buildscript")) {
insertAtIdx = i + 1;
break;
}
}
} else {
MethodMatcher buildPluginsMatcher = new MethodMatcher("RewriteGradleProject plugins(groovy.lang.Closure)");
MethodMatcher settingsPluginsMatcher = new MethodMatcher("RewriteSettings plugins(groovy.lang.Closure)");
J.MethodInvocation pluginDef = (J.MethodInvocation) ((J.Return) ((J.Block) ((J.Lambda) ((J.MethodInvocation) autoFormat(statement, ctx, getCursor())).getArguments().get(0)).getBody()).getStatements().get(0)).getExpression();
return cu.withStatements(ListUtils.map(cu.getStatements(), stat -> {
if (stat instanceof J.MethodInvocation) {
J.MethodInvocation m = (J.MethodInvocation) stat;
if (buildPluginsMatcher.matches(m) || settingsPluginsMatcher.matches(m)) {
m = m.withArguments(ListUtils.map(m.getArguments(), a -> {
if (a instanceof J.Lambda) {
J.Lambda l = (J.Lambda) a;
J.Block b = (J.Block) l.getBody();
List<Statement> pluginStatements = b.getStatements();
if (!pluginStatements.isEmpty() && pluginStatements.get(pluginStatements.size() - 1) instanceof J.Return) {
Statement last = pluginStatements.remove(pluginStatements.size() - 1);
Expression lastExpr = requireNonNull(((J.Return) last).getExpression());
pluginStatements.add(lastExpr.withPrefix(last.getPrefix()));
}
pluginStatements.add(pluginDef);
return l.withBody(autoFormat(b.withStatements(pluginStatements), ctx, getCursor()));
}
return a;
}));
return m;
}
if (insertAtIdx == 0) {
Comment licenseHeader = getLicenseHeader(cu);
if (licenseHeader != null) {
cu = removeLicenseHeader(cu);
statement = statement.withComments(Collections.singletonList(licenseHeader));
}
return stat;
}));
Space leadingSpace = Space.firstPrefix(cu.getStatements());
return cu.withStatements(ListUtils.insert(
Space.formatFirstPrefix(cu.getStatements(), leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace())),
autoFormat(statement, ctx, getCursor()),
insertAtIdx));
} else {
return cu.withStatements(ListUtils.insert(cu.getStatements(), autoFormat(statement.withPrefix(Space.format("\n\n")), ctx, getCursor()), insertAtIdx));
}
}
} else {
MethodMatcher buildPluginsMatcher = new MethodMatcher("RewriteGradleProject plugins(groovy.lang.Closure)");
MethodMatcher settingsPluginsMatcher = new MethodMatcher("RewriteSettings plugins(groovy.lang.Closure)");
J.MethodInvocation pluginDef = (J.MethodInvocation) ((J.Return) ((J.Block) ((J.Lambda) ((J.MethodInvocation) autoFormat(statement, ctx, getCursor())).getArguments().get(0)).getBody()).getStatements().get(0)).getExpression();
return cu.withStatements(ListUtils.map(cu.getStatements(), stat -> {
if (stat instanceof J.MethodInvocation) {
J.MethodInvocation m = (J.MethodInvocation) stat;
if (buildPluginsMatcher.matches(m) || settingsPluginsMatcher.matches(m)) {
m = m.withArguments(ListUtils.map(m.getArguments(), a -> {
if (a instanceof J.Lambda) {
J.Lambda l = (J.Lambda) a;
J.Block b = (J.Block) l.getBody();
List<Statement> pluginStatements = b.getStatements();
if (!pluginStatements.isEmpty() && pluginStatements.get(pluginStatements.size() - 1) instanceof J.Return) {
Statement last = pluginStatements.remove(pluginStatements.size() - 1);
Expression lastExpr = requireNonNull(((J.Return) last).getExpression());
pluginStatements.add(lastExpr.withPrefix(last.getPrefix()));
}
pluginStatements.add(pluginDef);
return l.withBody(autoFormat(b.withStatements(pluginStatements), ctx, getCursor()));
}
return a;
}));
return m;
}
}
return stat;
}));
}
return super.visitCompilationUnit(cu, ctx);
}
}
Original file line number Diff line number Diff line change
@@ -57,6 +57,15 @@ public class AddSettingsPlugin extends Recipe {
@Nullable
Boolean apply;

@Option(displayName = "Accept transitive",
description = "Some plugins apply other plugins. When this is set to true no plugin declaration will be added if the plugin is already applied transitively. " +
"When this is set to false the plugin will be added explicitly even if it is already applied transitively. " +
"Defaults to `true`.",
valid = {"true", "false"},
required = false)
@Nullable
Boolean acceptTransitive;

@Override
public String getDisplayName() {
return "Add Gradle settings plugin";
@@ -80,7 +89,7 @@ public Validated<Object> validate() {
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new IsSettingsGradle<>(),
new AddPluginVisitor(pluginId, StringUtils.isBlank(version) ? "latest.release" : version, versionPattern, apply)
new AddPluginVisitor(pluginId, StringUtils.isBlank(version) ? "latest.release" : version, versionPattern, apply, acceptTransitive)
);
}
}
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@
import org.openrewrite.gradle.marker.GradlePluginDescriptor;
import org.openrewrite.gradle.marker.GradleProject;
import org.openrewrite.gradle.marker.GradleSettings;
import org.openrewrite.gradle.util.ChangeStringLiteral;
import org.openrewrite.gradle.internal.ChangeStringLiteral;
import org.openrewrite.groovy.GroovyIsoVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.ListUtils;
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@
import org.openrewrite.marker.Markers;
import org.openrewrite.maven.MavenDownloadingException;
import org.openrewrite.maven.tree.GroupArtifact;
import org.openrewrite.style.Style;

import java.nio.file.Paths;
import java.util.Arrays;
@@ -275,7 +276,7 @@ private boolean withinMethodInvocations(List<String> methods) {
}

private String getIndent(G.CompilationUnit cu) {
TabsAndIndentsStyle style = cu.getStyle(TabsAndIndentsStyle.class, IntelliJ.tabsAndIndents());
TabsAndIndentsStyle style = Style.from(TabsAndIndentsStyle.class, cu, IntelliJ::tabsAndIndents);
if (style.getUseTabCharacter()) {
return "\t";
} else {
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@
import org.openrewrite.gradle.IsSettingsGradle;
import org.openrewrite.gradle.marker.GradleProject;
import org.openrewrite.gradle.marker.GradleSettings;
import org.openrewrite.gradle.util.ChangeStringLiteral;
import org.openrewrite.gradle.internal.ChangeStringLiteral;
import org.openrewrite.groovy.GroovyVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.ListUtils;
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.gradle.util.ChangeStringLiteral;
import org.openrewrite.gradle.internal.ChangeStringLiteral;
import org.openrewrite.groovy.GroovyVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.ListUtils;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 the original author or authors.
* Copyright 2025 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
@NullMarked
package org.openrewrite.gradle.util;
package org.openrewrite.gradle.style;

import org.jspecify.annotations.NullMarked;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
*/
@NullMarked
package org.openrewrite.gradle.table;

import org.jspecify.annotations.NullMarked;
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@
import org.openrewrite.Cursor;
import org.openrewrite.gradle.marker.GradleDependencyConfiguration;
import org.openrewrite.gradle.marker.GradleProject;
import org.openrewrite.gradle.util.DependencyStringNotationConverter;
import org.openrewrite.gradle.internal.DependencyStringNotationConverter;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.StringUtils;
import org.openrewrite.java.MethodMatcher;
@@ -93,11 +93,11 @@ public Matcher artifactId(@Nullable String artifactId) {
return null;
}

if (!(StringUtils.isBlank(configuration) || methodInvocation.getSimpleName().equals(configuration))) {
if (!StringUtils.isBlank(configuration) && !methodInvocation.getSimpleName().equals(configuration)) {
return null;
}

org.openrewrite.gradle.util.Dependency dependency = null;
org.openrewrite.gradle.internal.Dependency dependency = null;
Expression argument = methodInvocation.getArguments().get(0);
if (argument instanceof J.Literal || argument instanceof G.GString || argument instanceof G.MapEntry || argument instanceof G.MapLiteral) {
dependency = parseDependency(methodInvocation.getArguments());
@@ -203,7 +203,7 @@ private boolean withinDependencyConstraintsBlock(Cursor cursor) {
return withinBlock(cursor, "constraints") && withinDependenciesBlock(cursor);
}

private org.openrewrite.gradle.util.@Nullable Dependency parseDependency(List<Expression> arguments) {
private org.openrewrite.gradle.internal.@Nullable Dependency parseDependency(List<Expression> arguments) {
Expression argument = arguments.get(0);
if (argument instanceof J.Literal) {
return DependencyStringNotationConverter.parse((String) ((J.Literal) argument).getValue());
@@ -226,7 +226,7 @@ private boolean withinDependencyConstraintsBlock(Cursor cursor) {
return null;
}

private static org.openrewrite.gradle.util.@Nullable Dependency getMapEntriesDependency(List<Expression> arguments) {
private static org.openrewrite.gradle.internal.@Nullable Dependency getMapEntriesDependency(List<Expression> arguments) {
String group = null;
String artifact = null;

@@ -255,7 +255,7 @@ private boolean withinDependencyConstraintsBlock(Cursor cursor) {
return null;
}

return new org.openrewrite.gradle.util.Dependency(group, artifact, null, null, null);
return new org.openrewrite.gradle.internal.Dependency(group, artifact, null, null, null);
}
}
}
Original file line number Diff line number Diff line change
@@ -18,6 +18,8 @@
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.Checksum;
import org.openrewrite.ExecutionContext;
import org.openrewrite.HttpSenderExecutionContextView;
import org.openrewrite.ipc.http.HttpSender;

import java.io.IOException;
@@ -33,11 +35,15 @@ public class DistributionInfos {
@Nullable
Checksum wrapperJarChecksum;

static DistributionInfos fetch(HttpSender httpSender, GradleWrapper.DistributionType distributionType,
GradleWrapper.GradleVersion gradleVersion) throws IOException {
public static DistributionInfos fetch(GradleWrapper.DistributionType distributionType,
GradleWrapper.GradleVersion gradleVersion, ExecutionContext ctx) throws IOException {
HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender();
String downloadUrl = toDistTypeUrl(distributionType, gradleVersion.getDownloadUrl());
Checksum checksum = fetchChecksum(httpSender, toDistTypeUrl(distributionType, gradleVersion.getChecksumUrl()));
Checksum jarChecksum = fetchChecksum(httpSender, gradleVersion.getWrapperChecksumUrl());
Checksum jarChecksum = gradleVersion.getWrapperChecksumUrl() == null ?
null :
fetchChecksum(httpSender, gradleVersion.getWrapperChecksumUrl());

return new DistributionInfos(downloadUrl, checksum, jarChecksum);
}

Original file line number Diff line number Diff line change
@@ -22,9 +22,12 @@
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.gradle.internal.GradleWrapperScriptLoader;
import org.openrewrite.internal.StringUtils;
import org.openrewrite.ipc.http.HttpSender;
import org.openrewrite.remote.Remote;
import org.openrewrite.remote.RemoteArchive;
import org.openrewrite.remote.RemoteResource;
import org.openrewrite.semver.LatestRelease;
import org.openrewrite.semver.Semver;
import org.openrewrite.semver.VersionComparator;
@@ -66,38 +69,42 @@ public static GradleWrapper create(@Nullable String distributionTypeName, @Nulla
new LatestRelease(null) :
requireNonNull(Semver.validate(version, null).getValue());

HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getLargeFileHttpSender();
List<GradleVersion> allVersions = listAllVersions(repositoryUrl, ctx);
GradleVersion gradleVersion = allVersions.stream()
.filter(v -> versionComparator.isValid(null, v.version))
.max((v1, v2) -> versionComparator.compare(null, v1.version, v2.version))
.orElseThrow(() -> new IllegalStateException("Expected to find at least one Gradle wrapper version to select from."));

try {
DistributionInfos infos = DistributionInfos.fetch(distributionType, gradleVersion, ctx);
return new GradleWrapper(gradleVersion.version, infos);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

public static List<GradleVersion> listAllVersions(@Nullable String repositoryUrl, ExecutionContext ctx) {
HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender();
String gradleVersionsUrl = StringUtils.isBlank(repositoryUrl) ? "https://services.gradle.org/versions/all" : repositoryUrl;
try (HttpSender.Response resp = httpSender.send(httpSender.get(gradleVersionsUrl).build())) {
if (resp.isSuccessful()) {
List<GradleVersion> allVersions = new ObjectMapper()
return new ObjectMapper()
.registerModule(new ParameterNamesModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(resp.getBody(), new TypeReference<List<GradleVersion>>() {
});
GradleVersion gradleVersion = allVersions.stream()
.filter(v -> versionComparator.isValid(null, v.version))
.max((v1, v2) -> versionComparator.compare(null, v1.version, v2.version))
.orElseThrow(() -> new IllegalStateException("Expected to find at least one Gradle wrapper version to select from."));

DistributionInfos infos = DistributionInfos.fetch(httpSender, distributionType, gradleVersion);
return new GradleWrapper(gradleVersion.version, infos);
}
throw new IOException("Could not get Gradle versions at: " + gradleVersionsUrl);
throw new IOException("Could not get Gradle versions. HTTP " + resp.getCode());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

private static final Pattern GRADLE_VERSION_PATTERN = Pattern.compile("gradle-([0-9.]+)");

// Supports org.openrewrite.gradle.toolingapi.Assertions.withToolingApi(URI)
// This method is provided to support recipe development at organizations which require gradle to come from
// internal repositories. This is not used in contexts where services.gradle.org is accessible

/**
* Construct a Gradle wrapper from a URI.
* Can be used in contexts where servcies.gradle.org, normally used for version lookups, is unavailable.
* Can be used in contexts where services.gradle.org, normally used for version lookups, is unavailable.
*/
public static GradleWrapper create(URI fullDistributionUri, @SuppressWarnings("unused") ExecutionContext ctx) {
String version = "";
@@ -122,32 +129,22 @@ public String getPropertiesFormattedUrl() {

static final FileAttributes WRAPPER_JAR_FILE_ATTRIBUTES = new FileAttributes(null, null, null, true, true, false, 0);

public Remote wrapperJar() {
return Remote.builder(
WRAPPER_JAR_LOCATION,
URI.create(distributionInfos.getDownloadUrl())
).build("gradle-[^\\/]+\\/(?:.*\\/)+gradle-(plugins|wrapper)-(?!shared).*\\.jar", "gradle-wrapper.jar");
public RemoteArchive wrapperJar() {
return Remote.builder(WRAPPER_JAR_LOCATION)
.build(URI.create(distributionInfos.getDownloadUrl()), "gradle-[^\\/]+\\/(?:.*\\/)+gradle-(plugins|wrapper)-(?!shared).*\\.jar", "gradle-wrapper.jar");
}

public Remote wrapperJar(SourceFile before) {
return Remote.builder(
before,
URI.create(distributionInfos.getDownloadUrl())
).build("gradle-[^\\/]+\\/(?:.*\\/)+gradle-(plugins|wrapper)-(?!shared).*\\.jar", "gradle-wrapper.jar");
public RemoteArchive wrapperJar(SourceFile before) {
return Remote.builder(before)
.build(URI.create(distributionInfos.getDownloadUrl()), "gradle-[^\\/]+\\/(?:.*\\/)+gradle-(plugins|wrapper)-(?!shared).*\\.jar", "gradle-wrapper.jar");
}

public Remote gradlew() {
return Remote.builder(
WRAPPER_SCRIPT_LOCATION,
URI.create(distributionInfos.getDownloadUrl())
).build("gradle-[^\\/]+/(?:.*/)+gradle-plugins-.*\\.jar", "org/gradle/api/internal/plugins/unixStartScript.txt");
public RemoteResource gradlew() {
return new GradleWrapperScriptLoader().findNearest(version).gradlew();
}

public Remote gradlewBat() {
return Remote.builder(
WRAPPER_BATCH_LOCATION,
URI.create(distributionInfos.getDownloadUrl())
).build("gradle-[^\\/]+/(?:.*/)+gradle-plugins-.*\\.jar", "org/gradle/api/internal/plugins/windowsStartScript.txt");
public RemoteResource gradlewBat() {
return new GradleWrapperScriptLoader().findNearest(version).gradlewBat();
}

public enum DistributionType {
@@ -156,10 +153,15 @@ public enum DistributionType {
}

@Value
static class GradleVersion {
public static class GradleVersion {
String version;
String downloadUrl;
String checksumUrl;

/**
* Is null for every non-release version (e.g. RCs and milestones).
*/
@Nullable
String wrapperChecksumUrl;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#!/usr/bin/env bash

##############################################################################
##
## ${applicationName} start up script for UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: \$0 may be a link
PRG="\$0"
# Need this for relative symlinks.
while [ -h "\$PRG" ] ; do
ls=`ls -ld "\$PRG"`
link=`expr "\$ls" : '.*-> \\(.*\\)\$'`
if expr "\$link" : '/.*' > /dev/null; then
PRG="\$link"
else
PRG=`dirname "\$PRG"`"/\$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"\$PRG\"`/${appHomeRelativePath}" >/dev/null
APP_HOME="`pwd -P`"
cd "\$SAVED" >/dev/null

APP_NAME="${applicationName}"
APP_BASE_NAME=`basename "\$0"`

# Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script.
DEFAULT_JVM_OPTS=${defaultJvmOpts}

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
echo "\$*"
}

die ( ) {
echo
echo "\$*"
echo
exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac

CLASSPATH=$classpath

# Determine the Java command to use to start the JVM.
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
if [ ! -x "\$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "\$cygwin" = "false" -a "\$darwin" = "false" -a "\$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ \$? -eq 0 ] ; then
if [ "\$MAX_FD" = "maximum" -o "\$MAX_FD" = "max" ] ; then
MAX_FD="\$MAX_FD_LIMIT"
fi
ulimit -n \$MAX_FD
if [ \$? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: \$MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: \$MAX_FD_LIMIT"
fi
fi

# For Darwin, add options to specify how the application appears in the dock
if \$darwin; then
GRADLE_OPTS="\$GRADLE_OPTS \\"-Xdock:name=\$APP_NAME\\" \\"-Xdock:icon=\$APP_HOME/media/gradle.icns\\""
fi

# For Cygwin, switch paths to Windows format before running java
if \$cygwin ; then
APP_HOME=`cygpath --path --mixed "\$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "\$CLASSPATH"`
JAVACMD=`cygpath --unix "\$JAVACMD"`

# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in \$ROOTDIRSRAW ; do
ROOTDIRS="\$ROOTDIRS\$SEP\$dir"
SEP="|"
done
OURCYGPATTERN="(^(\$ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "\$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="\$OURCYGPATTERN|(\$GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "\$@" ; do
CHECK=`echo "\$arg"|egrep -c "\$OURCYGPATTERN" -`
CHECK2=`echo "\$arg"|egrep -c "^-"` ### Determine if an option

if [ \$CHECK -ne 0 ] && [ \$CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args\$i`=`cygpath --path --ignore --mixed "\$arg"`
else
eval `echo args\$i`="\"\$arg\""
fi
i=\$((i+1))
done
case \$i in
(0) set -- ;;
(1) set -- "\$args0" ;;
(2) set -- "\$args0" "\$args1" ;;
(3) set -- "\$args0" "\$args1" "\$args2" ;;
(4) set -- "\$args0" "\$args1" "\$args2" "\$args3" ;;
(5) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" ;;
(6) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" ;;
(7) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" ;;
(8) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" ;;
(9) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" "\$args8" ;;
esac
fi

# Split up the JVM_OPTS And ${optsEnvironmentVar} values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("\$@")
}
eval splitJvmOpts \$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar}
<% if ( appNameSystemProperty ) { %>JVM_OPTS[\${#JVM_OPTS[*]}]="-D${appNameSystemProperty}=\$APP_BASE_NAME"<% } %>

exec "\$JAVACMD" "\${JVM_OPTS[@]}" -classpath "\$CLASSPATH" ${mainClassName} "\$@"
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#!/usr/bin/env sh

##############################################################################
##
## ${applicationName} start up script for UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: \$0 may be a link
PRG="\$0"
# Need this for relative symlinks.
while [ -h "\$PRG" ] ; do
ls=`ls -ld "\$PRG"`
link=`expr "\$ls" : '.*-> \\(.*\\)\$'`
if expr "\$link" : '/.*' > /dev/null; then
PRG="\$link"
else
PRG=`dirname "\$PRG"`"/\$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"\$PRG\"`/${appHomeRelativePath}" >/dev/null
APP_HOME="`pwd -P`"
cd "\$SAVED" >/dev/null

APP_NAME="${applicationName}"
APP_BASE_NAME=`basename "\$0"`

# Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script.
DEFAULT_JVM_OPTS=${defaultJvmOpts}

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
echo "\$*"
}

die ( ) {
echo
echo "\$*"
echo
exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac

CLASSPATH=$classpath

# Determine the Java command to use to start the JVM.
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
if [ ! -x "\$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "\$cygwin" = "false" -a "\$darwin" = "false" -a "\$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ \$? -eq 0 ] ; then
if [ "\$MAX_FD" = "maximum" -o "\$MAX_FD" = "max" ] ; then
MAX_FD="\$MAX_FD_LIMIT"
fi
ulimit -n \$MAX_FD
if [ \$? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: \$MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: \$MAX_FD_LIMIT"
fi
fi

# For Darwin, add options to specify how the application appears in the dock
if \$darwin; then
GRADLE_OPTS="\$GRADLE_OPTS \\"-Xdock:name=\$APP_NAME\\" \\"-Xdock:icon=\$APP_HOME/media/gradle.icns\\""
fi

# For Cygwin, switch paths to Windows format before running java
if \$cygwin ; then
APP_HOME=`cygpath --path --mixed "\$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "\$CLASSPATH"`
JAVACMD=`cygpath --unix "\$JAVACMD"`

# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in \$ROOTDIRSRAW ; do
ROOTDIRS="\$ROOTDIRS\$SEP\$dir"
SEP="|"
done
OURCYGPATTERN="(^(\$ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "\$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="\$OURCYGPATTERN|(\$GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "\$@" ; do
CHECK=`echo "\$arg"|egrep -c "\$OURCYGPATTERN" -`
CHECK2=`echo "\$arg"|egrep -c "^-"` ### Determine if an option

if [ \$CHECK -ne 0 ] && [ \$CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args\$i`=`cygpath --path --ignore --mixed "\$arg"`
else
eval `echo args\$i`="\"\$arg\""
fi
i=\$((i+1))
done
case \$i in
(0) set -- ;;
(1) set -- "\$args0" ;;
(2) set -- "\$args0" "\$args1" ;;
(3) set -- "\$args0" "\$args1" "\$args2" ;;
(4) set -- "\$args0" "\$args1" "\$args2" "\$args3" ;;
(5) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" ;;
(6) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" ;;
(7) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" ;;
(8) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" ;;
(9) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" "\$args8" ;;
esac
fi

# Escape application args
for s in "\${@}" ; do
s=\"\$s\"
APP_ARGS=\$APP_ARGS" "\$s
done

# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- "\$DEFAULT_JVM_OPTS" "\$JAVA_OPTS" "\$${optsEnvironmentVar}" <% if ( appNameSystemProperty ) { %>"\"-D${appNameSystemProperty}=\$APP_BASE_NAME\"" <% } %>-classpath "\"\$CLASSPATH\"" ${mainClassName} "\$APP_ARGS"

# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "\$(uname)" = "Darwin" ] && [ "\$HOME" = "\$PWD" ]; then
cd "\$(dirname "\$0")"
fi

exec "\$JAVACMD" "\$@"
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
#!/bin/sh

#
# Copyright © 2015-2021 the original 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.
#
# SPDX-License-Identifier: Apache-2.0
#

##############################################################################
#
# ${applicationName} start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh ${applicationName}
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «\$var», «\${var}», «\${var:-default}», «\${var+SET}»,
# «\${var#prefix}», «\${var%suffix}», and «\$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "\$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and ${optsEnvironmentVar}) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#<% /*
# ... and if you're reading this, this IS the template just mentioned.
#
# This template is processed by
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/java/org/gradle/api/internal/plugins/UnixStartScriptGenerator.java
#
# Gradle is a meta-build system used by the project that you're building
# or installing. It's like autoconf but for projects that are written in
# Java and related languages. It's also used to build parts of the Gradle
# project itself.
#
# The Groovy template language is run in two phases.
#
# 1. Any character following \ is passed unmodified through to the
# next phase, while the \ is removed. Any other $ followed by
# varName or {varName} is replaced by the value of that variable.
#
# 2. The result of the first phase is parsed and run in a similar
# manner to JSP or MASON or PHP: anything within < % ... % > is a
# code block, anything else is sent as output, subject to the
# flow imposed by any code segments.
#
# 3. The "output" is a POSIX shell script, which has its own ideas about
# escaping with backslashes, so to get «\» you need to write «\\\\»
# and to get «$» you need to write «\\\$».
#
# For more details about the Groovy Template Engine, see
# https://docs.groovy-lang.org/next/html/documentation/ section §3.15
# (Template Engines) for details.
#
# (An example invocation of this template is from
# https://github.com/gradle/gradle/blob/HEAD/subprojects/build-init/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java
# within the Gradle project, which builds "gradlew".)
# */ %>
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################

# Attempt to set APP_HOME

# Resolve links: \$0 may be a link
app_path=\$0

# Need this for daisy-chained symlinks.
while
APP_HOME=\${app_path%"\${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "\$app_path" ]
do
ls=\$( ls -ld "\$app_path" )
link=\${ls#*' -> '}
case \$link in #(
/*) app_path=\$link ;; #(
*) app_path=\$APP_HOME\$link ;;
esac
done

# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=\${0##*/}
# Discard cd standard output in case \$CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=\$( cd -P "\${APP_HOME:-./}${appHomeRelativePath}" > /dev/null && printf '%s\n' "\$PWD" ) || exit

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

warn () {
echo "\$*"
} >&2

die () {
echo
echo "\$*"
echo
exit 1
} >&2

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "\$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac

CLASSPATH=$classpath
<% if ( mainClassName.startsWith('--module ') ) { %>
MODULE_PATH=$modulePath
<% } %>

# Determine the Java command to use to start the JVM.
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
if [ ! -x "\$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi

# Increase the maximum file descriptors if we can.
if ! "\$cygwin" && ! "\$darwin" && ! "\$nonstop" ; then
case \$MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=\$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case \$MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "\$MAX_FD" ||
warn "Could not set maximum file descriptor limit to \$MAX_FD"
esac
fi

# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and ${optsEnvironmentVar} environment variables.

# For Cygwin or MSYS, switch paths to Windows format before running java
if "\$cygwin" || "\$msys" ; then
APP_HOME=\$( cygpath --path --mixed "\$APP_HOME" )
CLASSPATH=\$( cygpath --path --mixed "\$CLASSPATH" )
<% if ( mainClassName.startsWith('--module ') ) { %> MODULE_PATH=\$( cygpath --path --mixed "\$MODULE_PATH" )<% } %>
JAVACMD=\$( cygpath --unix "\$JAVACMD" )

# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case \$arg in #(
-*) false ;; # don't mess with options #(
/?*) t=\${arg#/} t=/\${t%%/*} # looks like a POSIX filepath
[ -e "\$t" ] ;; #(
*) false ;;
esac
then
arg=\$( cygpath --path --ignore --mixed "\$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "\$@" "\$arg" # push replacement arg
done
fi

<% /*
# The DEFAULT_JVM_OPTS variable is intentionally defined here to allow using cygwin-processed APP_HOME.
# So far the only way to inject APP_HOME reference into DEFAULT_JVM_OPTS is to post-process the start script; the declaration is a good anchor to do that.
*/ %>
# Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script.
DEFAULT_JVM_OPTS=${defaultJvmOpts}

# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect \${Hostname} to be expanded, as it is an environment variable and will be
# treated as '\${Hostname}' itself on the command line.

set -- \\
<% if ( appNameSystemProperty ) {
%> "-D${appNameSystemProperty}=\$APP_BASE_NAME" \\
<% } %> -classpath "\$CLASSPATH" \\
<% if ( mainClassName.startsWith('--module ') ) {
%> --module-path "\$MODULE_PATH" \\
<% } %> ${mainClassName} \\
"\$@"

# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi

# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"\$var" ) &&
# set -- "\${ARGS[@]}" "\$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#

eval "set -- \$(
printf '%s\\n' "\$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar}" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |
tr '\\n' ' '
)" '"\$@"'

exec "\$JAVACMD" "\$@"
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
#!/bin/sh

#
# Copyright © 2015-2021 the original 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.
#

##############################################################################
#
# ${applicationName} start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh ${applicationName}
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «\$var», «\${var}», «\${var:-default}», «\${var+SET}»,
# «\${var#prefix}», «\${var%suffix}», and «\$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "\$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and ${optsEnvironmentVar}) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#<% /*
# ... and if you're reading this, this IS the template just mentioned.
#
# This template is processed by
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/java/org/gradle/api/internal/plugins/UnixStartScriptGenerator.java
#
# Gradle is a meta-build system used by the project that you're building
# or installing. It's like autoconf but for projects that are written in
# Java and related languages. It's also used to build parts of the Gradle
# project itself.
#
# The Groovy template language is run in two phases.
#
# 1. Any character following \ is passed unmodified through to the
# next phase, while the \ is removed. Any other $ followed by
# varName or {varName} is replaced by the value of that variable.
#
# 2. The result of the first phase is parsed and run in a similar
# manner to JSP or MASON or PHP: anything within < % ... % > is a
# code block, anything else is sent as output, subject to the
# flow imposed by any code segments.
#
# 3. The "output" is a POSIX shell script, which has its own ideas about
# escaping with backslashes, so to get «\» you need to write «\\\\»
# and to get «$» you need to write «\\\$».
#
# For more details about the Groovy Template Engine, see
# https://docs.groovy-lang.org/next/html/documentation/ section §3.15
# (Template Engines) for details.
#
# (An example invocation of this template is from
# https://github.com/gradle/gradle/blob/HEAD/subprojects/build-init/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java
# within the Gradle project, which builds "gradlew".)
# */ %>
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################

# Attempt to set APP_HOME

# Resolve links: \$0 may be a link
app_path=\$0

# Need this for daisy-chained symlinks.
while
APP_HOME=\${app_path%"\${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "\$app_path" ]
do
ls=\$( ls -ld "\$app_path" )
link=\${ls#*' -> '}
case \$link in #(
/*) app_path=\$link ;; #(
*) app_path=\$APP_HOME\$link ;;
esac
done

# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=\${0##*/}
# Discard cd standard output in case \$CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=\$( cd "\${APP_HOME:-./}${appHomeRelativePath}" > /dev/null && pwd -P ) || exit

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

warn () {
echo "\$*"
} >&2

die () {
echo
echo "\$*"
echo
exit 1
} >&2

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "\$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac

CLASSPATH=$classpath
<% if ( mainClassName.startsWith('--module ') ) { %>
MODULE_PATH=$modulePath
<% } %>

# Determine the Java command to use to start the JVM.
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
if [ ! -x "\$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi

# Increase the maximum file descriptors if we can.
if ! "\$cygwin" && ! "\$darwin" && ! "\$nonstop" ; then
case \$MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=\$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case \$MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "\$MAX_FD" ||
warn "Could not set maximum file descriptor limit to \$MAX_FD"
esac
fi

# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and ${optsEnvironmentVar} environment variables.

# For Cygwin or MSYS, switch paths to Windows format before running java
if "\$cygwin" || "\$msys" ; then
APP_HOME=\$( cygpath --path --mixed "\$APP_HOME" )
CLASSPATH=\$( cygpath --path --mixed "\$CLASSPATH" )
<% if ( mainClassName.startsWith('--module ') ) { %> MODULE_PATH=\$( cygpath --path --mixed "\$MODULE_PATH" )<% } %>
JAVACMD=\$( cygpath --unix "\$JAVACMD" )

# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case \$arg in #(
-*) false ;; # don't mess with options #(
/?*) t=\${arg#/} t=/\${t%%/*} # looks like a POSIX filepath
[ -e "\$t" ] ;; #(
*) false ;;
esac
then
arg=\$( cygpath --path --ignore --mixed "\$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "\$@" "\$arg" # push replacement arg
done
fi

<% /*
# The DEFAULT_JVM_OPTS variable is intentionally defined here to allow using cygwin-processed APP_HOME.
# So far the only way to inject APP_HOME reference into DEFAULT_JVM_OPTS is to post-process the start script; the declaration is a good anchor to do that.
*/ %>
# Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script.
DEFAULT_JVM_OPTS=${defaultJvmOpts}

# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect \${Hostname} to be expanded, as it is an environment variable and will be
# treated as '\${Hostname}' itself on the command line.

set -- \\
<% if ( appNameSystemProperty ) {
%> "-D${appNameSystemProperty}=\$APP_BASE_NAME" \\
<% } %> -classpath "\$CLASSPATH" \\
<% if ( mainClassName.startsWith('--module ') ) {
%> --module-path "\$MODULE_PATH" \\
<% } %> ${mainClassName} \\
"\$@"

# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi

# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"\$var" ) &&
# set -- "\${ARGS[@]}" "\$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#

eval "set -- \$(
printf '%s\\n' "\$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar}" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |
tr '\\n' ' '
)" '"\$@"'

exec "\$JAVACMD" "\$@"
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
#!/bin/sh

#
# Copyright © 2015-2021 the original 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.
#
# SPDX-License-Identifier: Apache-2.0
#

##############################################################################
#
# ${applicationName} start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh ${applicationName}
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «\$var», «\${var}», «\${var:-default}», «\${var+SET}»,
# «\${var#prefix}», «\${var%suffix}», and «\$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "\$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and ${optsEnvironmentVar}) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#<% /*
# ... and if you're reading this, this IS the template just mentioned.
#
# This template is processed by
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/java/org/gradle/api/internal/plugins/UnixStartScriptGenerator.java
#
# Gradle is a meta-build system used by the project that you're building
# or installing. It's like autoconf but for projects that are written in
# Java and related languages. It's also used to build parts of the Gradle
# project itself.
#
# The Groovy template language is run in two phases.
#
# 1. Any character following \ is passed unmodified through to the
# next phase, while the \ is removed. Any other $ followed by
# varName or {varName} is replaced by the value of that variable.
#
# 2. The result of the first phase is parsed and run in a similar
# manner to JSP or MASON or PHP: anything within < % ... % > is a
# code block, anything else is sent as output, subject to the
# flow imposed by any code segments.
#
# 3. The "output" is a POSIX shell script, which has its own ideas about
# escaping with backslashes, so to get «\» you need to write «\\\\»
# and to get «$» you need to write «\\\$».
#
# For more details about the Groovy Template Engine, see
# https://docs.groovy-lang.org/next/html/documentation/ section §3.15
# (Template Engines) for details.
#
# (An example invocation of this template is from
# https://github.com/gradle/gradle/blob/HEAD/subprojects/build-init/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java
# within the Gradle project, which builds "gradlew".)
# */ %>
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################

# Attempt to set APP_HOME

# Resolve links: \$0 may be a link
app_path=\$0

# Need this for daisy-chained symlinks.
while
APP_HOME=\${app_path%"\${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "\$app_path" ]
do
ls=\$( ls -ld "\$app_path" )
link=\${ls#*' -> '}
case \$link in #(
/*) app_path=\$link ;; #(
*) app_path=\$APP_HOME\$link ;;
esac
done

# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=\${0##*/}
# Discard cd standard output in case \$CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=\$( cd -P "\${APP_HOME:-./}${appHomeRelativePath}" > /dev/null && printf '%s\\n' "\$PWD" ) || exit

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

warn () {
echo "\$*"
} >&2

die () {
echo
echo "\$*"
echo
exit 1
} >&2

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "\$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac

CLASSPATH=$classpath
<% if ( mainClassName.startsWith('--module ') ) { %>
MODULE_PATH=$modulePath
<% } %>

# Determine the Java command to use to start the JVM.
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
if [ ! -x "\$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi

# Increase the maximum file descriptors if we can.
if ! "\$cygwin" && ! "\$darwin" && ! "\$nonstop" ; then
case \$MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=\$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case \$MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "\$MAX_FD" ||
warn "Could not set maximum file descriptor limit to \$MAX_FD"
esac
fi

# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and ${optsEnvironmentVar} environment variables.

# For Cygwin or MSYS, switch paths to Windows format before running java
if "\$cygwin" || "\$msys" ; then
APP_HOME=\$( cygpath --path --mixed "\$APP_HOME" )
CLASSPATH=\$( cygpath --path --mixed "\$CLASSPATH" )
<% if ( mainClassName.startsWith('--module ') ) { %> MODULE_PATH=\$( cygpath --path --mixed "\$MODULE_PATH" )<% } %>
JAVACMD=\$( cygpath --unix "\$JAVACMD" )

# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case \$arg in #(
-*) false ;; # don't mess with options #(
/?*) t=\${arg#/} t=/\${t%%/*} # looks like a POSIX filepath
[ -e "\$t" ] ;; #(
*) false ;;
esac
then
arg=\$( cygpath --path --ignore --mixed "\$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "\$@" "\$arg" # push replacement arg
done
fi

<% /*
# The DEFAULT_JVM_OPTS variable is intentionally defined here to allow using cygwin-processed APP_HOME.
# So far the only way to inject APP_HOME reference into DEFAULT_JVM_OPTS is to post-process the start script; the declaration is a good anchor to do that.
*/ %>
# Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script.
DEFAULT_JVM_OPTS=${defaultJvmOpts}

# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect \${Hostname} to be expanded, as it is an environment variable and will be
# treated as '\${Hostname}' itself on the command line.

set -- \\
<% if ( appNameSystemProperty ) {
%> "-D${appNameSystemProperty}=\$APP_BASE_NAME" \\
<% } %> -classpath "\$CLASSPATH" \\
<% if ( mainClassName.startsWith('--module ') ) {
%> --module-path "\$MODULE_PATH" \\
<% } %> ${mainClassName} \\
"\$@"

# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi

# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"\$var" ) &&
# set -- "\${ARGS[@]}" "\$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#

eval "set -- \$(
printf '%s\\n' "\$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar}" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |
tr '\\n' ' '
)" '"\$@"'

exec "\$JAVACMD" "\$@"
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
#!/bin/sh

#
# Copyright © 2015-2021 the original 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.
#

##############################################################################
#
# ${applicationName} start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh ${applicationName}
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «\$var», «\${var}», «\${var:-default}», «\${var+SET}»,
# «\${var#prefix}», «\${var%suffix}», and «\$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "\$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and ${optsEnvironmentVar}) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#<% /*
# ... and if you're reading this, this IS the template just mentioned.
#
# This template is processed by
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/java/org/gradle/api/internal/plugins/UnixStartScriptGenerator.java
#
# Gradle is a meta-build system used by the project that you're building
# or installing. It's like autoconf but for projects that are written in
# Java and related languages. It's also used to build parts of the Gradle
# project itself.
#
# The Groovy template language is run in two phases.
#
# 1. Any character following \ is passed unmodified through to the
# next phase, while the \ is removed. Any other $ followed by
# varName or {varName} is replaced by the value of that variable.
#
# 2. The result of the first phase is parsed and run in a similar
# manner to JSP or MASON or PHP: anything within < % ... % > is a
# code block, anything else is sent as output, subject to the
# flow imposed by any code segments.
#
# 3. The "output" is a POSIX shell script, which has its own ideas about
# escaping with backslashes, so to get «\» you need to write «\\\\»
# and to get «$» you need to write «\\\$».
#
# For more details about the Groovy Template Engine, see
# https://docs.groovy-lang.org/next/html/documentation/ section §3.15
# (Template Engines) for details.
#
# (An example invocation of this template is from
# https://github.com/gradle/gradle/blob/HEAD/subprojects/build-init/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java
# within the Gradle project, which builds "gradlew".)
# */ %>
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################

# Attempt to set APP_HOME

# Resolve links: \$0 may be a link
app_path=\$0

# Need this for daisy-chained symlinks.
while
APP_HOME=\${app_path%"\${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "\$app_path" ]
do
ls=\$( ls -ld "\$app_path" )
link=\${ls#*' -> '}
case \$link in #(
/*) app_path=\$link ;; #(
*) app_path=\$APP_HOME\$link ;;
esac
done

# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=\${0##*/}
# Discard cd standard output in case \$CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=\$( cd "\${APP_HOME:-./}${appHomeRelativePath}" > /dev/null && pwd -P ) || exit

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

warn () {
echo "\$*"
} >&2

die () {
echo
echo "\$*"
echo
exit 1
} >&2

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "\$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac

CLASSPATH=$classpath
<% if ( mainClassName.startsWith('--module ') ) { %>
MODULE_PATH=$modulePath
<% } %>

# Determine the Java command to use to start the JVM.
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
if [ ! -x "\$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi

# Increase the maximum file descriptors if we can.
if ! "\$cygwin" && ! "\$darwin" && ! "\$nonstop" ; then
case \$MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=\$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case \$MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "\$MAX_FD" ||
warn "Could not set maximum file descriptor limit to \$MAX_FD"
esac
fi

# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and ${optsEnvironmentVar} environment variables.

# For Cygwin or MSYS, switch paths to Windows format before running java
if "\$cygwin" || "\$msys" ; then
APP_HOME=\$( cygpath --path --mixed "\$APP_HOME" )
CLASSPATH=\$( cygpath --path --mixed "\$CLASSPATH" )
<% if ( mainClassName.startsWith('--module ') ) { %> MODULE_PATH=\$( cygpath --path --mixed "\$MODULE_PATH" )<% } %>
JAVACMD=\$( cygpath --unix "\$JAVACMD" )

# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case \$arg in #(
-*) false ;; # don't mess with options #(
/?*) t=\${arg#/} t=/\${t%%/*} # looks like a POSIX filepath
[ -e "\$t" ] ;; #(
*) false ;;
esac
then
arg=\$( cygpath --path --ignore --mixed "\$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "\$@" "\$arg" # push replacement arg
done
fi

<% /*
# The DEFAULT_JVM_OPTS variable is intentionally defined here to allow using cygwin-processed APP_HOME.
# So far the only way to inject APP_HOME reference into DEFAULT_JVM_OPTS is to post-process the start script; the declaration is a good anchor to do that.
*/ %>
# Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script.
DEFAULT_JVM_OPTS=${defaultJvmOpts}

# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect \${Hostname} to be expanded, as it is an environment variable and will be
# treated as '\${Hostname}' itself on the command line.

set -- \\
<% if ( appNameSystemProperty ) {
%> "-D${appNameSystemProperty}=\$APP_BASE_NAME" \\
<% } %> -classpath "\$CLASSPATH" \\
<% if ( mainClassName.startsWith('--module ') ) {
%> --module-path "\$MODULE_PATH" \\
<% } %> ${mainClassName} \\
"\$@"

# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi

# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"\$var" ) &&
# set -- "\${ARGS[@]}" "\$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#

eval "set -- \$(
printf '%s\\n' "\$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar}" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |
tr '\\n' ' '
)" '"\$@"'

exec "\$JAVACMD" "\$@"
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#!/bin/bash

##############################################################################
##
## ${applicationName} start up script for UN*X
##
##############################################################################

# Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

APP_NAME="${applicationName}"
APP_BASE_NAME=`basename "\$0"`

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
echo "\$*"
}

die ( ) {
echo
echo "\$*"
echo
exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac

# For Cygwin, ensure paths are in UNIX format before anything is touched.
if \$cygwin ; then
[ -n "\$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "\$JAVA_HOME"`
fi

# Attempt to set APP_HOME
# Resolve links: \$0 may be a link
PRG="\$0"
# Need this for relative symlinks.
while [ -h "\$PRG" ] ; do
ls=`ls -ld "\$PRG"`
link=`expr "\$ls" : '.*-> \\(.*\\)\$'`
if expr "\$link" : '/.*' > /dev/null; then
PRG="\$link"
else
PRG=`dirname "\$PRG"`"/\$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"\$PRG\"`/${appHomeRelativePath}"
APP_HOME="`pwd -P`"
cd "\$SAVED"

CLASSPATH=$classpath

# Determine the Java command to use to start the JVM.
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
if [ ! -x "\$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "\$cygwin" = "false" -a "\$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ \$? -eq 0 ] ; then
if [ "\$MAX_FD" = "maximum" -o "\$MAX_FD" = "max" ] ; then
MAX_FD="\$MAX_FD_LIMIT"
fi
ulimit -n \$MAX_FD
if [ \$? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: \$MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: \$MAX_FD_LIMIT"
fi
fi

# For Darwin, add options to specify how the application appears in the dock
if \$darwin; then
GRADLE_OPTS="\$GRADLE_OPTS \\"-Xdock:name=\$APP_NAME\\" \\"-Xdock:icon=\$APP_HOME/media/gradle.icns\\""
fi

# For Cygwin, switch paths to Windows format before running java
if \$cygwin ; then
APP_HOME=`cygpath --path --mixed "\$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "\$CLASSPATH"`

# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in \$ROOTDIRSRAW ; do
ROOTDIRS="\$ROOTDIRS\$SEP\$dir"
SEP="|"
done
OURCYGPATTERN="(^(\$ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "\$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="\$OURCYGPATTERN|(\$GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "\$@" ; do
CHECK=`echo "\$arg"|egrep -c "\$OURCYGPATTERN" -`
CHECK2=`echo "\$arg"|egrep -c "^-"` ### Determine if an option

if [ \$CHECK -ne 0 ] && [ \$CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args\$i`=`cygpath --path --ignore --mixed "\$arg"`
else
eval `echo args\$i`="\"\$arg\""
fi
i=\$((i+1))
done
case \$i in
(0) set -- ;;
(1) set -- "\$args0" ;;
(2) set -- "\$args0" "\$args1" ;;
(3) set -- "\$args0" "\$args1" "\$args2" ;;
(4) set -- "\$args0" "\$args1" "\$args2" "\$args3" ;;
(5) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" ;;
(6) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" ;;
(7) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" ;;
(8) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" ;;
(9) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" "\$args8" ;;
esac
fi

# Split up the JVM_OPTS And ${optsEnvironmentVar} values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("\$@")
}
eval splitJvmOpts \$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar}
<% if ( appNameSystemProperty ) { %>JVM_OPTS[\${#JVM_OPTS[*]}]="-D${appNameSystemProperty}=\$APP_BASE_NAME"<% } %>

exec "\$JAVACMD" "\${JVM_OPTS[@]}" -classpath "\$CLASSPATH" ${mainClassName} "\$@"
Loading