This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch wrapper-rewrite
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 3ae1e1aab6b419238eae5a6ebb26075f367c4b68
Author: James Daugherty <jdaughe...@jdresources.net>
AuthorDate: Tue May 13 15:45:06 2025 -0400

    Rework grails wrapper to support multiple versions and track them 
historically.
---
 ...aseHandler.java => FindLastReleaseHandler.java} |   2 +-
 ...otHandler.java => FindLastSnapshotHandler.java} |   2 +-
 .../src/main/java/grails/init/GrailsHome.java      |  53 +++++---
 .../main/java/grails/init/GrailsReleaseType.java   |  28 +++++
 .../src/main/java/grails/init/GrailsUpdater.java   | 137 ++++++++++++++-------
 .../src/main/java/grails/init/GrailsVersion.java   |  22 ++--
 .../main/java/grails/init/GrailsWrapperRepo.java   |  82 ++++++++++++
 .../src/main/java/grails/init/Start.java           |  89 ++++++++++---
 8 files changed, 321 insertions(+), 94 deletions(-)

diff --git a/grails-wrapper/src/main/java/grails/init/FindReleaseHandler.java 
b/grails-wrapper/src/main/java/grails/init/FindLastReleaseHandler.java
similarity index 97%
rename from grails-wrapper/src/main/java/grails/init/FindReleaseHandler.java
rename to grails-wrapper/src/main/java/grails/init/FindLastReleaseHandler.java
index 9153586f9a..988111edc8 100644
--- a/grails-wrapper/src/main/java/grails/init/FindReleaseHandler.java
+++ b/grails-wrapper/src/main/java/grails/init/FindLastReleaseHandler.java
@@ -23,7 +23,7 @@ import org.xml.sax.helpers.DefaultHandler;
 /**
  * Created by jameskleeh on 11/2/16.
  */
-public class FindReleaseHandler extends DefaultHandler {
+public class FindLastReleaseHandler extends DefaultHandler {
 
     private String releaseVersion;
     private String latestVersion;
diff --git a/grails-wrapper/src/main/java/grails/init/FindSnapshotHandler.java 
b/grails-wrapper/src/main/java/grails/init/FindLastSnapshotHandler.java
similarity index 97%
rename from grails-wrapper/src/main/java/grails/init/FindSnapshotHandler.java
rename to grails-wrapper/src/main/java/grails/init/FindLastSnapshotHandler.java
index ba766f5d2a..0995acd93d 100644
--- a/grails-wrapper/src/main/java/grails/init/FindSnapshotHandler.java
+++ b/grails-wrapper/src/main/java/grails/init/FindLastSnapshotHandler.java
@@ -19,7 +19,7 @@ package grails.init;
 import org.xml.sax.Attributes;
 import org.xml.sax.helpers.DefaultHandler;
 
-public class FindSnapshotHandler extends DefaultHandler {
+public class FindLastSnapshotHandler extends DefaultHandler {
 
     private boolean insideSnapshotVersion = false;
     private boolean insideVersion = false;
diff --git a/grails-wrapper/src/main/java/grails/init/GrailsHome.java 
b/grails-wrapper/src/main/java/grails/init/GrailsHome.java
index b92b87ecd1..e2969ec3a5 100644
--- a/grails-wrapper/src/main/java/grails/init/GrailsHome.java
+++ b/grails-wrapper/src/main/java/grails/init/GrailsHome.java
@@ -31,10 +31,12 @@ public class GrailsHome {
     public final File home;
     public final File wrapperDirectory;
     public final List<GrailsVersion> versions;
+    public final List<GrailsReleaseType> allowedReleaseTypes;
     public final GrailsVersion latestVersion;
 
-    public GrailsHome(String grailsHome) throws IOException {
-        home = findGrailsHome(grailsHome).getCanonicalFile();
+    public GrailsHome(List<GrailsReleaseType> allowedReleaseTypes, String 
forcedGrailsHome) throws IOException {
+        home = findGrailsHome(forcedGrailsHome).getCanonicalFile();
+        this.allowedReleaseTypes = allowedReleaseTypes == null ? new 
ArrayList<>() : allowedReleaseTypes;
 
         wrapperDirectory = new File(home, "wrapper");
         if(!wrapperDirectory.exists()) {
@@ -107,16 +109,16 @@ public class GrailsHome {
         GrailsVersion lastMilestone = null;
         GrailsVersion lastSnapshot = null;
         for (GrailsVersion version : versions) {
-            if(version.releaseType == GrailsVersion.ReleaseType.RELEASE && 
(lastRelease == null || version.compareTo(lastRelease) > 0)) {
+            if(version.releaseType == GrailsReleaseType.RELEASE && 
(lastRelease == null || version.compareTo(lastRelease) > 0)) {
                 lastRelease = version;
             }
-            else if(version.releaseType == GrailsVersion.ReleaseType.RC && 
(lastReleaseCandidate == null || version.compareTo(lastReleaseCandidate) > 0)) {
+            else if(version.releaseType == GrailsReleaseType.RC && 
(lastReleaseCandidate == null || version.compareTo(lastReleaseCandidate) > 0)) {
                 lastReleaseCandidate = version;
             }
-            else if(version.releaseType == GrailsVersion.ReleaseType.MILESTONE 
&& (lastMilestone == null || version.compareTo(lastMilestone) > 0)) {
+            else if(version.releaseType == GrailsReleaseType.MILESTONE && 
(lastMilestone == null || version.compareTo(lastMilestone) > 0)) {
                 lastMilestone = version;
             }
-            else if(version.releaseType == GrailsVersion.ReleaseType.SNAPSHOT 
&& (lastSnapshot == null || version.compareTo(lastSnapshot) > 0)) {
+            else if(version.releaseType == GrailsReleaseType.SNAPSHOT && 
(lastSnapshot == null || version.compareTo(lastSnapshot) > 0)) {
                 lastSnapshot = version;
             }
         }
@@ -151,6 +153,9 @@ public class GrailsHome {
 
             try {
                 GrailsVersion version = new GrailsVersion(child.getName());
+                if(!allowedReleaseTypes.isEmpty() && 
!allowedReleaseTypes.contains(version.releaseType)) {
+                    continue;
+                }
                 versions.add(version);
             }
             catch(Exception ignored) {
@@ -169,13 +174,14 @@ public class GrailsHome {
      * 2. using the environment variable
      * 3. Looking in the current directory for a GRAILS_HOME_MARKERS or for a 
.grails directory
      * and all parent directories.  If none, is found, the current directory 
will be returned.
+     * There is a special case for the current directory if inside of the 
grails core repository.
      *
      * @return the GRAILS_HOME directory
      * @throws IOException if canonicalization fails
      */
-    public static File findGrailsHome(String possibleGrailsHome) throws 
IOException {
-        if (possibleGrailsHome != null && !possibleGrailsHome.isEmpty()) {
-            return validateGrailsHome(possibleGrailsHome, "Specified Grails 
Home");
+    public static File findGrailsHome(String grailsHomeOverride) throws 
IOException {
+        if (grailsHomeOverride != null && !grailsHomeOverride.isEmpty()) {
+            return validateGrailsHome(grailsHomeOverride, "Specified Grails 
Home");
         }
 
         String environmentOverride = System.getenv("GRAILS_HOME");
@@ -204,9 +210,15 @@ public class GrailsHome {
         return file.exists();
     }
 
+    private static boolean directoryExists(File baseDirectory, String name) {
+        File file = new File(baseDirectory, name);
+        return file.exists() && file.isDirectory();
+    }
+
     /**
      * Locate the “Grails" home by first looking in `directory` for a 
GRAILS_HOME_MARKERS or for a .grails directory
-     * and all parent directories.  If none, is found, the original directory 
will be returned.
+     * and all parent directories.  If none, is found, the original directory 
will be returned.  Short circuit on the
+     * home directory to avoid traversing into the root if possible.
      *
      * @param directory where to begin the search
      * @return the directory containing one of the markers, or original 
directory
@@ -220,27 +232,34 @@ public class GrailsHome {
         File userHome = new 
File(System.getProperty("user.home")).getCanonicalFile();
 
         File searchDirectory = directory.getCanonicalFile();
+
         if (searchDirectory.equals(userHome)) {
-            throw new IllegalStateException("GRAILS_HOME is no longer allowed 
to be at the user home directory.");
+            // if run from the user home directory, allow it to exist
+            return searchDirectory;
         }
 
         if (searchDirectory.getParentFile() == null) {
-            throw new IllegalStateException("GRAILS_HOME is not permitted to 
be at the root of the file system.");
+            // if run from the root, allow it to exist
+            return searchDirectory;
         }
 
         File originalDirectory = searchDirectory;
         while (searchDirectory != null && searchDirectory.exists() && 
!searchDirectory.equals(userHome)) {
+            if (directoryExists(searchDirectory, ".grails")) {
+                return searchDirectory;
+            }
+
+            // Assume this is nested under the grails core directory, so 
assume the current directory is the root of the project
+            if(directoryExists(searchDirectory, "grails-core") && 
directoryExists(searchDirectory, "grails-bom")) {
+                return originalDirectory;
+            }
+
             for (String name : GRAILS_HOME_MARKERS) {
                 if (exists(searchDirectory, name)) {
                     return searchDirectory;
                 }
             }
 
-            File grailsDir = new File(searchDirectory, ".grails");
-            if (grailsDir.exists() && grailsDir.isDirectory()) {
-                return searchDirectory;
-            }
-
             searchDirectory = searchDirectory.getParentFile();
             if (searchDirectory != null) {
                 searchDirectory = searchDirectory.getCanonicalFile();
diff --git a/grails-wrapper/src/main/java/grails/init/GrailsReleaseType.java 
b/grails-wrapper/src/main/java/grails/init/GrailsReleaseType.java
new file mode 100644
index 0000000000..1e982d1251
--- /dev/null
+++ b/grails-wrapper/src/main/java/grails/init/GrailsReleaseType.java
@@ -0,0 +1,28 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package grails.init;
+
+public enum GrailsReleaseType {
+    RELEASE,
+    RC,
+    MILESTONE,
+    SNAPSHOT;
+
+    boolean isSnapshot() {
+        return this == SNAPSHOT;
+    }
+}
diff --git a/grails-wrapper/src/main/java/grails/init/GrailsUpdater.java 
b/grails-wrapper/src/main/java/grails/init/GrailsUpdater.java
index dd9927e731..d8abe099d6 100644
--- a/grails-wrapper/src/main/java/grails/init/GrailsUpdater.java
+++ b/grails-wrapper/src/main/java/grails/init/GrailsUpdater.java
@@ -30,26 +30,39 @@ import java.net.URL;
 import java.nio.channels.Channels;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.file.Files;
+import java.util.List;
 
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 
 public class GrailsUpdater {
-    private static final String WRAPPER_MAVEN_PATH = "/org/apache/grails/" + 
GrailsHome.CLI_COMBINED_PROJECT_NAME;
-    private static final String GRAILS_RELEASE_MAVEN_REPO_BASE_URL = 
"https://repository.apache.org/content/groups/public";;
 
     private final GrailsHome grailsHome;
+    private final GrailsVersion preferredVersion;
     private GrailsVersion updatedVersion;
 
-    public GrailsUpdater() throws IOException {
-        this(null);
+    public GrailsUpdater(List<GrailsReleaseType> allowedTypes, GrailsVersion 
preferredVersion) throws IOException {
+        this(allowedTypes, preferredVersion, null);
     }
 
-    public GrailsUpdater(String possibleGrailsHome) throws IOException {
-        grailsHome = new GrailsHome(possibleGrailsHome);
+    public GrailsUpdater(List<GrailsReleaseType> allowedTypes, GrailsVersion 
preferredVersion, String possibleGrailsHome) throws IOException {
+        grailsHome = new GrailsHome(allowedTypes, possibleGrailsHome);
+        this.preferredVersion = preferredVersion;
     }
 
-    public File getExecutedVersion() {
-        return updatedVersion == null ? 
grailsHome.getLatestWrapperImplementation() : 
grailsHome.getWrapperImplementation(grailsHome.getVersionDirectory(updatedVersion));
+    public GrailsVersion getSelectedVersion() {
+        if(preferredVersion != null) {
+            return preferredVersion;
+        }
+
+        if(updatedVersion != null) {
+            return updatedVersion;
+        }
+
+        return grailsHome.latestVersion;
+    }
+
+    public File getExecutedJarFile() {
+        return 
grailsHome.getWrapperImplementation(grailsHome.getVersionDirectory(getSelectedVersion()));
     }
 
     /**
@@ -63,43 +76,57 @@ public class GrailsUpdater {
             return true;
         }
 
-        // TODO: Should we force updates for snapshots?
+        if(preferredVersion != null) {
+            if(!grailsHome.versions.contains(preferredVersion)) {
+                return true;
+            }
+
+            // Force snapshots to update always
+            return preferredVersion.releaseType.isSnapshot();
+        }
 
         return false;
     }
 
     public boolean update() {
-        GrailsVersion baseVersion = null;
-        try {
-            baseVersion = getVersion();
+        GrailsWrapperRepo repo = GrailsWrapperRepo.getSelectedRepo();
+
+        GrailsVersion latestVersion = null;
+        if(preferredVersion != null) {
+            latestVersion = preferredVersion;
         }
-        catch(Exception e) {
-            System.err.println("You must be connected to the internet the 
first time you use the Grails wrapper");
-            e.printStackTrace();
-            System.exit(1);
+        else {
+            try {
+                latestVersion = getLastVersion(repo);
+            }
+            catch(Exception e) {
+                System.err.println("Unable to fetch latest Grails CLI.");
+                e.printStackTrace();
+                System.exit(1);
+            }
         }
 
         String detailedVersion = null;
-        if (baseVersion.releaseType.isSnapshot()) {
+        if (latestVersion.releaseType.isSnapshot()) {
             try {
-                detailedVersion = getSnapshotVersion(baseVersion);
+                detailedVersion = fetchSnapshotForVersion(repo, latestVersion);
             }
             catch(Exception e) {
-                System.err.println("Could not parse snapshot version.  You 
must be connected to the internet the first time you use the Grails wrapper");
+                System.err.println("Could not parse snapshot version from 
maven metadata.");
                 e.printStackTrace();
                 System.exit(1);
             }
         }
 
-        boolean theResult = updateJar(baseVersion, detailedVersion);
+        boolean theResult = updateJar(repo, latestVersion, detailedVersion);
         if(theResult) {
-            updatedVersion = baseVersion;
+            updatedVersion = latestVersion;
         }
 
         return theResult;
     }
 
-    private boolean updateJar(GrailsVersion version, String snapshotVersion) {
+    private boolean updateJar(GrailsWrapperRepo repo, GrailsVersion version, 
String snapshotVersion) {
         boolean success = false;
 
         final String localJarFilename = GrailsHome.CLI_COMBINED_PROJECT_NAME + 
"-" + version.version;
@@ -108,9 +135,22 @@ public class GrailsUpdater {
 
         try {
             File downloadedJar = File.createTempFile(localJarFilename, 
jarFileExtension);
-            String wrapperUrl = getMavenBaseUrl() + WRAPPER_MAVEN_PATH + "/" + 
version.version + "/" + remoteJarFilename + jarFileExtension;
-            HttpURLConnection conn = createHttpURLConnection(wrapperUrl);
-            success = downloadWrapperJar(version, downloadedJar, 
conn.getInputStream());
+            String wrapperUrl = repo.getFileUrl(version, remoteJarFilename + 
jarFileExtension);
+
+            InputStream inputStream;
+            if(repo.isFile) {
+                File jarFile = new File(wrapperUrl);
+                if(!jarFile.exists()) {
+                    throw new IllegalStateException("Could not determine local 
metadata file from local maven repository: " + jarFile.getAbsolutePath() + " 
does not exist");
+                }
+                inputStream = Files.newInputStream(jarFile.toPath());
+            }
+            else {
+                HttpURLConnection conn = createHttpURLConnection(wrapperUrl);
+                inputStream = conn.getInputStream();
+            }
+
+            success = downloadWrapperJar(version, downloadedJar, inputStream);
         } catch (Exception e) {
             System.err.println("There was an error downloading the wrapper 
jar");
             e.printStackTrace();
@@ -138,30 +178,33 @@ public class GrailsUpdater {
         return true;
     }
 
-    private static String getMavenBaseUrl() {
-        String baseUrl = System.getProperty("grails.maven.repo.baseUrl");
-        if (baseUrl != null) {
-            return baseUrl;
+    private static InputStream retrieveMavenMetadata(GrailsWrapperRepo repo, 
String metadataUrl) throws IOException {
+        if(repo.isFile) {
+            File metadataFile = new File(metadataUrl);
+            if(!metadataFile.exists()) {
+                throw new IllegalStateException("Could not determine local 
metadata file from local maven repository: " + metadataFile.getAbsolutePath() + 
" does not exist");
+            }
+            return Files.newInputStream(metadataFile.toPath());
         }
-
-        baseUrl = System.getenv("GRAILS_RELEASE_MAVEN_REPO_BASE_URL");
-        if (baseUrl != null) {
-            return baseUrl;
+        else {
+            HttpURLConnection connection = 
createHttpURLConnection(metadataUrl);
+            try {
+                return connection.getInputStream();
+            }
+            catch(Exception e) {
+                throw new RuntimeException("There was an error downloading the 
metadata file", e);
+            }
         }
-
-        return GRAILS_RELEASE_MAVEN_REPO_BASE_URL;
     }
 
-    private static GrailsVersion getVersion() throws IOException, 
SAXException, ParserConfigurationException {
+    private GrailsVersion getLastVersion(GrailsWrapperRepo repo) throws 
IOException, SAXException, ParserConfigurationException {
         SAXParserFactory factory = SAXParserFactory.newInstance();
         SAXParser saxParser = factory.newSAXParser();
-        FindReleaseHandler findReleaseHandler = new FindReleaseHandler();
-        final String mavenMetadataFileUrl = getMavenBaseUrl() + 
WRAPPER_MAVEN_PATH + "/maven-metadata.xml";
-        HttpURLConnection conn = createHttpURLConnection(mavenMetadataFileUrl);
+        FindLastReleaseHandler findLastReleaseHandler = new 
FindLastReleaseHandler();
 
-        try(InputStream stream = conn.getInputStream()) {
-            saxParser.parse(stream, findReleaseHandler);
-            String parsedVersion = findReleaseHandler.getVersion();
+        try(InputStream stream = retrieveMavenMetadata(repo, 
repo.getRootMetadataUrl())) {
+            saxParser.parse(stream, findLastReleaseHandler);
+            String parsedVersion = findLastReleaseHandler.getVersion();
             try {
                 return new GrailsVersion(parsedVersion);
             }
@@ -171,14 +214,14 @@ public class GrailsUpdater {
         }
     }
 
-    private static String getSnapshotVersion(GrailsVersion baseVersion) throws 
IOException, SAXException, ParserConfigurationException {
+    private String fetchSnapshotForVersion(GrailsWrapperRepo repo, 
GrailsVersion baseVersion) throws IOException, SAXException, 
ParserConfigurationException {
         System.out.println("A Grails snapshot version has been detected.  
Downloading latest snapshot.");
+
         SAXParserFactory factory = SAXParserFactory.newInstance();
         SAXParser saxParser = factory.newSAXParser();
-        FindSnapshotHandler findVersionHandler = new FindSnapshotHandler();
-        final String mavenMetadataFileUrl = getMavenBaseUrl() + 
WRAPPER_MAVEN_PATH + "/" + baseVersion.version + "/maven-metadata.xml";
-        HttpURLConnection conn = createHttpURLConnection(mavenMetadataFileUrl);
-        try(InputStream stream = conn.getInputStream()) {
+        FindLastSnapshotHandler findVersionHandler = new 
FindLastSnapshotHandler();
+
+        try(InputStream stream = retrieveMavenMetadata(repo, 
repo.getMetadataUrl(baseVersion))) {
             saxParser.parse(stream, findVersionHandler);
             return findVersionHandler.getVersion();
         }
diff --git a/grails-wrapper/src/main/java/grails/init/GrailsVersion.java 
b/grails-wrapper/src/main/java/grails/init/GrailsVersion.java
index eb61551632..6949fd1251 100644
--- a/grails-wrapper/src/main/java/grails/init/GrailsVersion.java
+++ b/grails-wrapper/src/main/java/grails/init/GrailsVersion.java
@@ -33,7 +33,7 @@ public class GrailsVersion implements 
Comparable<GrailsVersion> {
     public final String major;
     public final String minor;
     public final String patch;
-    public final ReleaseType releaseType;
+    public final GrailsReleaseType releaseType;
     public final int patchNumber;
 
     GrailsVersion(String version) {
@@ -54,16 +54,16 @@ public class GrailsVersion implements 
Comparable<GrailsVersion> {
 
         Matcher m;
         if ((m = RELEASE.matcher(patch)).matches()) {
-            releaseType = ReleaseType.RELEASE;
+            releaseType = GrailsReleaseType.RELEASE;
             patchNumber = Integer.parseInt(m.group(1));
         } else if ((m = RC.matcher(patch)).matches()) {
-            releaseType = ReleaseType.RC;
+            releaseType = GrailsReleaseType.RC;
             patchNumber = Integer.parseInt(m.group(1));
         } else if ((m = MILESTONE.matcher(patch)).matches()) {
-            releaseType = ReleaseType.MILESTONE;
+            releaseType = GrailsReleaseType.MILESTONE;
             patchNumber = Integer.parseInt(m.group(1));
         } else if ((m = SNAPSHOT.matcher(patch)).matches()) {
-            releaseType = ReleaseType.SNAPSHOT;
+            releaseType = GrailsReleaseType.SNAPSHOT;
             patchNumber = Integer.parseInt(m.group(1));
         } else {
             throw new IllegalArgumentException("Unrecognized patch version: " 
+ patch);
@@ -109,14 +109,8 @@ public class GrailsVersion implements 
Comparable<GrailsVersion> {
         return Integer.compare(patchNumber, o.patchNumber);
     }
 
-    public enum ReleaseType {
-        RELEASE,
-        RC,
-        MILESTONE,
-        SNAPSHOT;
-
-        boolean isSnapshot() {
-            return this == SNAPSHOT;
-        }
+    @Override
+    public String toString() {
+        return version;
     }
 }
diff --git a/grails-wrapper/src/main/java/grails/init/GrailsWrapperRepo.java 
b/grails-wrapper/src/main/java/grails/init/GrailsWrapperRepo.java
new file mode 100644
index 0000000000..4346879cf3
--- /dev/null
+++ b/grails-wrapper/src/main/java/grails/init/GrailsWrapperRepo.java
@@ -0,0 +1,82 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package grails.init;
+
+import java.io.File;
+
+public class GrailsWrapperRepo {
+    private String baseUrl;
+    private String repoPath;
+    private String metadataName;
+    boolean isFile;
+
+    private GrailsWrapperRepo() {
+    }
+
+    String getUrl() {
+        return join(baseUrl, repoPath);
+    }
+
+    String getRootMetadataUrl() {
+        return join(getUrl(), metadataName);
+    }
+
+    String getMetadataUrl(GrailsVersion version) {
+        return join(getUrl(), version.version, metadataName);
+    }
+
+    String getFileUrl(GrailsVersion version, String name) {
+        return join(getUrl(), version.version, name);
+
+    }
+
+    private String join(String ... elements) {
+        return String.join(isFile ? File.separator : "/", elements);
+    }
+
+    static GrailsWrapperRepo getSelectedRepo() {
+        GrailsWrapperRepo repo = new GrailsWrapperRepo();
+        repo.repoPath = "org/apache/grails/" + 
GrailsHome.CLI_COMBINED_PROJECT_NAME;
+
+        String configured = getConfiguredMavenUrl();
+        if (configured != null) {
+            System.out.println("... Update Repository is overridden to: " + 
configured);
+            repo.baseUrl = configured;
+        } else {
+            // default to upstream snapshots or groups
+            repo.baseUrl = 
"https://repository.apache.org/content/groups/public";;
+        }
+        repo.isFile = !repo.baseUrl.startsWith("http");
+
+        if((repo.isFile && repo.baseUrl.endsWith(File.separator)) || 
(!repo.isFile && repo.baseUrl.endsWith("/"))) {
+            // remove trailing slash
+            repo.baseUrl = repo.baseUrl.substring(0, repo.baseUrl.length() - 
1);
+        }
+
+        repo.metadataName = repo.isFile ? "maven-metadata-local.xml" : 
"maven-metadata.xml";
+        return repo;
+    }
+
+    static String getConfiguredMavenUrl() {
+        String baseUrl = System.getProperty("grails.repo.url");
+        if (baseUrl != null) {
+            return baseUrl;
+        }
+
+        return System.getenv("GRAILS_REPO_URL");
+    }
+}
diff --git a/grails-wrapper/src/main/java/grails/init/Start.java 
b/grails-wrapper/src/main/java/grails/init/Start.java
index e51f19b541..a9d9efe69a 100644
--- a/grails-wrapper/src/main/java/grails/init/Start.java
+++ b/grails-wrapper/src/main/java/grails/init/Start.java
@@ -18,40 +18,44 @@ package grails.init;
 
 import grails.proxy.SystemPropertiesAuthenticator;
 
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
+import java.io.FileInputStream;
 import java.io.InputStream;
 import java.lang.reflect.Method;
 import java.net.Authenticator;
-import java.net.HttpURLConnection;
 import java.net.URL;
 import java.net.URLClassLoader;
-import java.nio.channels.Channels;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.file.Files;
 import java.util.Arrays;
-
-import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import java.util.List;
+import java.util.Properties;
+import java.util.stream.Collectors;
 
 /**
  * The purpose of this class is to download the expanded Grails wrapper jars 
into GRAILS_HOME (`.grails` in the project root)
  * This class is not meant to be distributed as part of SDKMAN since we'll 
distribute the expanded jars with it.
- * After downloading the jars, it will delegate to the downloaded wrapper impl 
project
+ * After downloading the jars, it will delegate to the downloaded grails-cli 
project.
+ *
+ * There are 3 ways this class can be used:
+ * 1. in testing a grails release (run from a non-project directory) // will 
require GRAILS_HOME to be manually set
+ * 2. running from a non-project directory (end user usage)
+ * 3. running from inside a grails project
  */
 public class Start {
     public static void main(String[] args) {
         Authenticator.setDefault(new SystemPropertiesAuthenticator());
 
         try {
-            GrailsUpdater updater = new GrailsUpdater();
+            List<GrailsReleaseType> allowedTypes = getAllowedReleaseTypes();
+            GrailsVersion preferredGrailsVersion = getPreferredGrailsVersion();
+
+            GrailsUpdater updater = new GrailsUpdater(allowedTypes, 
preferredGrailsVersion);
             boolean forceUpdate = (args.length > 0 && 
args[0].trim().equals("update-wrapper"));
 
+            boolean updated = false;
             String[] adjustedArgs = args;
             if (forceUpdate || updater.needsUpdating()) {
-                updater.update();
+                System.out.println("Updating Grails wrapper...");
+                updated = updater.update();
 
                 // remove "update-wrapper" command argument
                 if (forceUpdate) {
@@ -59,7 +63,11 @@ public class Start {
                 }
             }
 
-            URLClassLoader child = new URLClassLoader(new 
URL[]{updater.getExecutedVersion().toURI().toURL()});
+            if(updated) {
+                System.out.println("Updated wrapper to version: " + 
updater.getSelectedVersion().toString());
+            }
+
+            URLClassLoader child = new URLClassLoader(new 
URL[]{updater.getExecutedJarFile().toURI().toURL()});
             Class<?> classToLoad = Class.forName("grails.init.RunCommand", 
true, child);
             Method main = classToLoad.getMethod("main", String[].class);
             main.invoke(null, (Object[]) adjustedArgs);
@@ -68,4 +76,57 @@ public class Start {
             System.exit(1);
         }
     }
+
+    private static GrailsVersion getPreferredGrailsVersion() {
+        // Check for a properties file in case inside a grails project
+        File gradleProperties = new File("gradle.properties");
+        if(!gradleProperties.exists()) {
+            return null;
+        }
+
+        Properties properties = new Properties();
+        try {
+            try (InputStream in = new FileInputStream(gradleProperties)) {
+                properties.load(in);
+            }
+        }
+        catch(Exception e) {
+            System.err.println("Failed to load gradle.properties from "+ 
gradleProperties);
+            e.printStackTrace();
+            System.exit(1);
+        }
+
+        if(!properties.containsKey("grailsVersion")) {
+            return null;
+        }
+
+        String grailsVersion = properties.getProperty("grailsVersion");
+        if(grailsVersion == null) {
+            System.out.println("gradle.properties does not contain 
grailsVersion; downloading latest Grails Version");
+            return null;
+        }
+
+        try {
+            return new GrailsVersion(grailsVersion);
+        }
+        catch(Exception e) {
+            System.out.println("An invalid Grails Version [" + grailsVersion + 
"] was specified in gradle.properties");
+            e.printStackTrace();
+            System.exit(1);
+        }
+
+        return null;
+    }
+
+    private static List<GrailsReleaseType> getAllowedReleaseTypes() {
+        String raw = System.getenv("GRAILS_WRAPPER_ALLOWED_TYPES");
+        if (raw == null || raw.trim().isEmpty()) {
+            return null;
+        }
+
+        return Arrays.stream(raw.split(","))
+                .map(String::trim)
+                .map(GrailsReleaseType::valueOf)
+                .collect(Collectors.toList());
+    }
 }

Reply via email to