diff a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -37,26 +37,62 @@
 - submit - example pull request test runner
 
 ## Building
 
 [JDK 12](http://jdk.java.net/12/) or later and [Gradle](https://gradle.org/)
-5.2.1 or later is required for building. To build the project on macOS or
-GNU/Linux, just run the following command from the source tree root:
+5.6.2 or later is required for building. To build the project on macOS or
+GNU/Linux x64, just run the following command from the source tree root:
 
 ```bash
 $ sh gradlew
 ```
 
-To build the project on Windows, run the following command from the source tree root:
+To build the project on Windows x64, run the following command from the source tree root:
 
 ```bat
 > gradlew
 ```
 
 The extracted jlinked image will end up in the `build` directory in the source
 tree root.
 
+### Other operating systems and CPU architectures
+
+If you want to build on an operating system other than GNU/Linux, macOS or
+Windows _or_ if you want to build on a CPU architecture other than x64, then
+ensure that you have JDK 12 or later installed locally. You can then run the
+following command from the source tree root:
+
+```bash
+$ sh gradlew
+```
+
+The extracted jlinked image will end up in the `build` directory in the source
+tree root.
+
+### Offline builds
+
+If you don't want the build to automatically download any dependencies, then
+you must ensure that you have installed the following software locally:
+
+- JDK 12 or later
+- Gradle 5.6.2 or later
+
+To create a build then run the command
+
+```bash
+$ gradle offline
+```
+
+_Please note_ that the above command does _not_ make use of `gradlew` to avoid
+downloading Gradle.
+
+The extracted jlinked image will end up in the `build` directory in the source
+tree root.
+
+### Cross-linking
+
 It is also supported to cross-jlink jimages to GNU/Linux, macOS and/or Windows from
 any of the aforementioned operating systems. To build all applicable jimages
 (including the server-side tooling), run the following command from the
 source tree root:
 
diff a/bots/cli/build.gradle b/bots/cli/build.gradle
--- a/bots/cli/build.gradle
+++ b/bots/cli/build.gradle
@@ -62,11 +62,11 @@
 
     testImplementation project(':test')
 }
 
 images {
-    linux {
+    linux_x64 {
         modules = ['jdk.crypto.ec',
                    'org.openjdk.skara.bots.pr',
                    'org.openjdk.skara.bots.hgbridge',
                    'org.openjdk.skara.bots.forward',
                    'org.openjdk.skara.bots.notify',
diff a/build.gradle b/build.gradle
--- a/build.gradle
+++ b/build.gradle
@@ -102,22 +102,82 @@
 
 reproduce {
     dockerfile = 'test.dockerfile'
 }
 
+def getOS() {
+    def os = System.getProperty('os.name').toLowerCase()
+    if (os.startsWith('linux')) {
+        return 'linux'
+    }
+    if (os.startsWith('mac')) {
+        return 'macos'
+    }
+    if (os.startsWith('win')) {
+        return 'windows'
+    }
+    if (os.startsWith('sunos')) {
+        return 'solaris'
+    }
+    throw new GradleException("Unexpected operating system: " + os)
+}
+
+def getCPU() {
+    def cpu = System.getProperty('os.arch').toLowerCase()
+    if (cpu.startsWith('amd64') || cpu.startsWith('x86_64') || cpu.startsWith('x64')) {
+        return 'x64'
+    }
+    if (cpu.startsWith('x86') || cpu.startsWith('i386')) {
+        return 'x86'
+    }
+    if (cpu.startsWith('sparc')) {
+        return 'sparc'
+    }
+    if (cpu.startsWith('ppc')) {
+        return 'ppc'
+    }
+    if (cpu.startsWith('arm')) {
+        return 'arm'
+    }
+    throw new GradleException("Unexpected operating system: " + cpu)
+}
+
 task local(type: Copy) {
     doFirst {
         delete project.buildDir
     }
-    def os = System.getProperty('os.name').toLowerCase()
-    def osName = os.startsWith('win') ? 'Windows' :
-        os.startsWith('mac') ? 'Macos' : 'Linux'
 
-    dependsOn ':cli:image' + osName
+    def os = getOS()
+    def cpu = getCPU()
+
+    if (os in ['linux', 'macos', 'windows'] && cpu == 'x64') {
+        def target = os.substring(0, 1).toUpperCase() + os.substring(1) +
+                     cpu.substring(0, 1).toUpperCase() + cpu.substring(1)
+        dependsOn ':cli:image' + target
+    } else {
+        dependsOn ':cli:imageLocal'
+    }
+
+    from zipTree(file(project.rootDir.toString() +
+                      '/cli/build/distributions/cli' +
+                      '-' + project.version + '-' +
+                      os + '-' + cpu + '.zip'))
+    into project.buildDir
+}
+
+task offline(type: Copy) {
+    doFirst {
+        delete project.buildDir
+    }
+
+    def os = getOS()
+    def cpu = getCPU()
+
+    dependsOn ':cli:imageLocal'
     from zipTree(file(project.rootDir.toString() +
                       '/cli/build/distributions/cli' +
                       '-' + project.version + '-' +
-		      osName.toLowerCase() + '.zip'))
+                      os + '-' + cpu + '.zip'))
     into project.buildDir
 }
 
 defaultTasks 'local'
diff a/buildSrc/images/src/main/java/org/openjdk/skara/gradle/images/ImagesPlugin.java b/buildSrc/images/src/main/java/org/openjdk/skara/gradle/images/ImagesPlugin.java
--- a/buildSrc/images/src/main/java/org/openjdk/skara/gradle/images/ImagesPlugin.java
+++ b/buildSrc/images/src/main/java/org/openjdk/skara/gradle/images/ImagesPlugin.java
@@ -27,13 +27,53 @@
 import org.gradle.api.file.Directory;
 import org.gradle.api.tasks.bundling.*;
 import org.gradle.api.artifacts.UnknownConfigurationException;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.io.File;
 
 public class ImagesPlugin implements Plugin<Project> {
+    private static String getOS() {
+        var p = System.getProperty("os.name").toLowerCase();
+        if (p.startsWith("win")) {
+            return "windows";
+        }
+        if (p.startsWith("mac")) {
+            return "macos";
+        }
+        if (p.startsWith("linux")) {
+            return "linux";
+        }
+        if (p.startsWith("sunos")) {
+            return "solaris";
+        }
+
+        throw new RuntimeException("Unknown operating system: " + System.getProperty("os.name"));
+    }
+
+    private static String getCPU() {
+        var p = System.getProperty("os.arch").toLowerCase();
+        if (p.startsWith("amd64") || p.startsWith("x86_64") || p.startsWith("x64")) {
+            return "x64";
+        }
+        if (p.startsWith("x86") || p.startsWith("i386")) {
+            return "x86";
+        }
+        if (p.startsWith("sparc")) {
+            return "sparc";
+        }
+        if (p.startsWith("ppc")) {
+            return "ppc";
+        }
+        if (p.startsWith("arm")) {
+            return "arm";
+        }
+
+        throw new RuntimeException("Unknown CPU: " + System.getProperty("os.arch"));
+    }
+
     @Override
     public void apply(Project project) {
         NamedDomainObjectContainer<ImageEnvironment> imageEnvironmentContainer =
             project.container(ImageEnvironment.class, new NamedDomainObjectFactory<ImageEnvironment>() {
                 public ImageEnvironment create(String name) {
@@ -47,19 +87,27 @@
         var rootDir = project.getRootDir().toPath().toAbsolutePath();
         var buildDir = project.getBuildDir().toPath().toAbsolutePath();
 
         imageEnvironmentContainer.all(new Action<ImageEnvironment>() {
             public void execute(ImageEnvironment env) {
-                var name = env.getName();
-                var subName = name.substring(0, 1).toUpperCase() + name.substring(1);
+                var parts = env.getName().split("_");;
+                var isLocal = parts.length == 1 && parts[0].equals("local");
+                var os = isLocal ? getOS() : parts[0];
+                var cpu = isLocal ? getCPU() : parts[1];
+                var osAndCpuPascalCased =
+                    os.substring(0, 1).toUpperCase() + os.substring(1) +
+                    cpu.substring(0, 1).toUpperCase() + cpu.substring(1);
+                var subName = isLocal ? "Local" : osAndCpuPascalCased;
 
                 var downloadTaskName = "download" + subName + "JDK";
-                project.getTasks().register(downloadTaskName, DownloadJDKTask.class, (task) -> {
-                    task.getUrl().set(env.getUrl());
-                    task.getSha256().set(env.getSha256());
-                    task.getToDir().set(rootDir.resolve(".jdk"));
-                });
+                if (!isLocal) {
+                    project.getTasks().register(downloadTaskName, DownloadJDKTask.class, (task) -> {
+                        task.getUrl().set(env.getUrl());
+                        task.getSha256().set(env.getSha256());
+                        task.getToDir().set(rootDir.resolve(".jdk"));
+                    });
+                }
 
                 var linkTaskName = "link" + subName;
                 project.getTasks().register(linkTaskName, LinkTask.class, (task) -> {
                     for (var jarTask : project.getTasksByName("jar", true)) {
                         if (jarTask instanceof Jar) {
@@ -73,48 +121,55 @@
                         task.dependsOn(runtimeClasspath);
                     } catch (UnknownConfigurationException e) {
                         // ignored
                     }
 
-                    task.dependsOn(projectPath + ":" + downloadTaskName);
+                    if (!isLocal) {
+                        task.dependsOn(projectPath + ":" + downloadTaskName);
+                        task.getUrl().set(env.getUrl());
+                    } else {
+                        task.getUrl().set("local");
+                    }
                     task.getToDir().set(buildDir.resolve("images"));
-                    task.getUrl().set(env.getUrl());
-                    task.getOS().set(name);
+                    task.getOS().set(os);
+                    task.getCPU().set(cpu);
                     task.getLaunchers().set(env.getLaunchers());
                     task.getModules().set(env.getModules());
                 });
 
                 var launchersTaskName = "launchers" + subName;
                 project.getTasks().register(launchersTaskName, LaunchersTask.class, (task) -> {
                     task.getLaunchers().set(env.getLaunchers());
                     task.getOptions().set(env.getOptions());
                     task.getToDir().set(buildDir.resolve("launchers"));
-                    task.getOS().set(name);
+                    task.getOS().set(os);
+                    task.getCPU().set(cpu);
                 });
 
                 var zipTaskName = "bundleZip" + subName;
                 project.getTasks().register(zipTaskName, Zip.class, (task) -> {
                     task.dependsOn(projectPath + ":" + linkTaskName);
                     task.dependsOn(projectPath + ":" + launchersTaskName);
 
                     task.setPreserveFileTimestamps(false);
                     task.setReproducibleFileOrder(true);
                     task.getArchiveBaseName().set(project.getName());
-                    task.getArchiveClassifier().set(name);
+                    task.getArchiveClassifier().set(os + "-" + cpu);
                     task.getArchiveExtension().set("zip");
 
                     if (env.getMan().isPresent()) {
                         var root = project.getRootProject().getRootDir().toPath().toAbsolutePath();
                         task.from(root.resolve(env.getMan().get()).toString(), (s) -> {
                             s.into("bin/man");
                         });
                     }
 
-                    task.from(buildDir.resolve("images").resolve(name), (s) -> {
+                    var subdir = os + "-" + cpu;
+                    task.from(buildDir.resolve("images").resolve(subdir), (s) -> {
                         s.into("image");
                     });
-                    task.from(buildDir.resolve("launchers").resolve(name), (s) -> {
+                    task.from(buildDir.resolve("launchers").resolve(subdir), (s) -> {
                         s.into("bin");
                     });
                 });
 
                 var gzipTaskName = "bundleTarGz" + subName;
@@ -123,25 +178,26 @@
                     task.dependsOn(projectPath + ":" + launchersTaskName);
 
                     task.setPreserveFileTimestamps(false);
                     task.setReproducibleFileOrder(true);
                     task.getArchiveBaseName().set(project.getName());
-                    task.getArchiveClassifier().set(name);
+                    task.getArchiveClassifier().set(os + "-" + cpu);
                     task.getArchiveExtension().set("tar.gz");
                     task.setCompression(Compression.GZIP);
 
                     if (env.getMan().isPresent()) {
                         var root = project.getRootProject().getRootDir().toPath().toAbsolutePath();
                         task.from(root.resolve(env.getMan().get()).toString(), (s) -> {
                             s.into("bin/man");
                         });
                     }
 
-                    task.from(buildDir.resolve("images").resolve(name), (s) -> {
+                    var subdir = os + "-" + cpu;
+                    task.from(buildDir.resolve("images").resolve(subdir), (s) -> {
                         s.into("image");
                     });
-                    task.from(buildDir.resolve("launchers").resolve(name), (s) -> {
+                    task.from(buildDir.resolve("launchers").resolve(subdir), (s) -> {
                         s.into("bin");
                     });
                 });
 
                 var imageTaskName = "image" + subName;
diff a/buildSrc/images/src/main/java/org/openjdk/skara/gradle/images/LaunchersTask.java b/buildSrc/images/src/main/java/org/openjdk/skara/gradle/images/LaunchersTask.java
--- a/buildSrc/images/src/main/java/org/openjdk/skara/gradle/images/LaunchersTask.java
+++ b/buildSrc/images/src/main/java/org/openjdk/skara/gradle/images/LaunchersTask.java
@@ -35,17 +35,19 @@
 import java.util.Comparator;
 
 public class LaunchersTask extends DefaultTask {
     private Property<Path> toDir;
     private Property<String> os;
+    private Property<String> cpu;
     private MapProperty<String, String> launchers;
     private ListProperty<String> options;
 
     @Inject
     public LaunchersTask(ObjectFactory factory) {
         toDir = factory.property(Path.class);
         os = factory.property(String.class);
+        cpu = factory.property(String.class);
         launchers = factory.mapProperty(String.class, String.class);
         options = factory.listProperty(String.class);
     }
 
     @Input
@@ -61,10 +63,15 @@
     @Input
     Property<String> getOS() {
         return os;
     }
 
+    @Input
+    Property<String> getCPU() {
+        return cpu;
+    }
+
     @Input
     MapProperty<String, String> getLaunchers() {
         return launchers;
     }
 
@@ -75,11 +82,11 @@
                 .forEach(File::delete);
     }
 
     @TaskAction
     void generate() throws IOException {
-        var dest = toDir.get().resolve(os.get());
+        var dest = toDir.get().resolve(os.get() + "-" + cpu.get());
         if (Files.isDirectory(dest)) {
             clearDirectory(dest);
         }
         Files.createDirectories(dest);
         var optionString = String.join(" ", options.get());
diff a/buildSrc/images/src/main/java/org/openjdk/skara/gradle/images/LinkTask.java b/buildSrc/images/src/main/java/org/openjdk/skara/gradle/images/LinkTask.java
--- a/buildSrc/images/src/main/java/org/openjdk/skara/gradle/images/LinkTask.java
+++ b/buildSrc/images/src/main/java/org/openjdk/skara/gradle/images/LinkTask.java
@@ -39,20 +39,22 @@
 import java.util.*;
 import java.util.stream.Collectors;
 
 public class LinkTask extends DefaultTask {
     private final Property<String> os;
+    private final Property<String> cpu;
     private final Property<String> url;
     private final Property<Path> toDir;
     private final MapProperty<String, String> launchers;
     private final ListProperty<String> modules;
     private final SetProperty<RegularFile> modulePath;
     private final SetProperty<FileSystemLocation> runtimeModules;
 
     @Inject
     public LinkTask(ObjectFactory factory) {
         os = factory.property(String.class);
+        cpu = factory.property(String.class);
         url = factory.property(String.class);
         toDir = factory.property(Path.class);
         launchers = factory.mapProperty(String.class, String.class);
         modules = factory.listProperty(String.class);
         modulePath = factory.setProperty(RegularFile.class);
@@ -67,10 +69,15 @@
     @Input
     Property<String> getOS() {
         return os;
     }
 
+    @Input
+    Property<String> getCPU() {
+        return cpu;
+    }
+
     @Input
     Property<String> getUrl() {
         return url;
     }
 
@@ -115,13 +122,18 @@
         }
         for (var jar : runtimeModules.get()) {
             modularJars.add(jar.getAsFile().toString());
         }
 
-        var filename = Path.of(URI.create(url.get()).getPath()).getFileName().toString();
-        var dirname = filename.replace(".zip", "").replace(".tar.gz", "");
-        var jdk = project.getRootDir().toPath().toAbsolutePath().resolve(".jdk").resolve(dirname);
+        Path jdk = null;
+        if (!url.get().equals("local")) {
+            var filename = Path.of(URI.create(url.get()).getPath()).getFileName().toString();
+            var dirname = filename.replace(".zip", "").replace(".tar.gz", "");
+            jdk = project.getRootDir().toPath().toAbsolutePath().resolve(".jdk").resolve(dirname);
+        } else {
+            jdk = Path.of(System.getProperty("java.home"));
+        }
         var dirs = Files.walk(jdk)
                         .filter(Files::isDirectory)
                         .filter(p -> p.getFileName().toString().equals("jmods"))
                         .collect(Collectors.toList());
         if (dirs.size() != 1) {
@@ -141,11 +153,11 @@
         }
         uniqueModules.addAll(modules.get());
         var allModules = new ArrayList<String>(uniqueModules);
 
         Files.createDirectories(toDir.get());
-        var dest = toDir.get().resolve(os.get());
+        var dest = toDir.get().resolve(os.get() + "-" + cpu.get());
         if (Files.exists(dest) && Files.isDirectory(dest)) {
             clearDirectory(dest);
         }
 
         Collections.sort(modulePath);
@@ -161,18 +173,18 @@
                                        "--vm", "server",
                                        "--output", dest.toString());
         });
 
         var currentOS = System.getProperty("os.name").toLowerCase().substring(0, 3);
-        if (currentOS.equals(os.get().substring(0, 3))) {
+        if (os.get().equals("local") || currentOS.equals(os.get().substring(0, 3))) {
             var ext = currentOS.startsWith("win") ? ".exe" : "";
             var javaLaunchers = Files.walk(dest)
                                      .filter(Files::isExecutable)
                                      .filter(p -> p.getFileName().toString().equals("java" + ext))
                                      .collect(Collectors.toList());
             if (javaLaunchers.size() != 1) {
-                throw new GradleException("Multiple or no java launchers generated for " + os.get() + " image");
+                throw new GradleException("Multiple or no java launchers generated for " + os.get() + "-" + cpu.get() + " image");
             }
             var java = javaLaunchers.get(0);
             project.exec((spec) -> {
                 spec.setCommandLine(java, "-Xshare:dump", "-version");
             });
diff a/cli/build.gradle b/cli/build.gradle
--- a/cli/build.gradle
+++ b/cli/build.gradle
@@ -69,37 +69,44 @@
         'git-publish': 'org.openjdk.skara.cli/org.openjdk.skara.cli.GitPublish'
     ]
 
     ext.modules = ['jdk.crypto.ec']
 
-    windows {
+    windows_x64 {
         modules = ext.modules
         launchers = ext.launchers
         bundles = ['zip', 'tar.gz']
         jdk {
             url = 'https://download.java.net/java/GA/jdk12/GPL/openjdk-12_windows-x64_bin.zip'
             sha256 = '35a8d018f420fb05fe7c2aa9933122896ca50bd23dbd373e90d8e2f3897c4e92'
         }
     }
 
-    linux {
+    linux_x64 {
         modules = ext.modules
         launchers = ext.launchers
         man = 'cli/resources/man'
         bundles = ['zip', 'tar.gz']
         jdk {
             url = 'https://download.java.net/java/GA/jdk12/GPL/openjdk-12_linux-x64_bin.tar.gz'
             sha256 = 'b43bc15f4934f6d321170419f2c24451486bc848a2179af5e49d10721438dd56'
         }
     }
 
-    macos {
+    macos_x64 {
         modules = ext.modules
         launchers = ext.launchers
         man = 'cli/resources/man'
         bundles = ['zip', 'tar.gz']
         jdk {
             url = 'https://download.java.net/java/GA/jdk12/GPL/openjdk-12_osx-x64_bin.tar.gz'
             sha256 = '52164a04db4d3fdfe128cfc7b868bc4dae52d969f03d53ae9d4239fe783e1a3a'
         }
     }
+
+    local {
+        modules = ext.modules
+        launchers = ext.launchers
+        man = 'cli/resources/man'
+        bundles = ['zip', 'tar.gz']
+    }
 }
diff a/deps.env b/deps.env
--- a/deps.env
+++ b/deps.env
@@ -1,11 +1,11 @@
-JDK_LINUX_URL="https://download.java.net/java/GA/jdk12/GPL/openjdk-12_linux-x64_bin.tar.gz"
-JDK_LINUX_SHA256="b43bc15f4934f6d321170419f2c24451486bc848a2179af5e49d10721438dd56"
+JDK_LINUX_X64_URL="https://download.java.net/java/GA/jdk12/GPL/openjdk-12_linux-x64_bin.tar.gz"
+JDK_LINUX_X64_SHA256="b43bc15f4934f6d321170419f2c24451486bc848a2179af5e49d10721438dd56"
 
-JDK_MACOS_URL="https://download.java.net/java/GA/jdk12/GPL/openjdk-12_osx-x64_bin.tar.gz"
-JDK_MACOS_SHA256="52164a04db4d3fdfe128cfc7b868bc4dae52d969f03d53ae9d4239fe783e1a3a"
+JDK_MACOS_X64_URL="https://download.java.net/java/GA/jdk12/GPL/openjdk-12_osx-x64_bin.tar.gz"
+JDK_MACOS_X64_SHA256="52164a04db4d3fdfe128cfc7b868bc4dae52d969f03d53ae9d4239fe783e1a3a"
 
-JDK_WINDOWS_URL="https://download.java.net/java/GA/jdk12/GPL/openjdk-12_windows-x64_bin.zip"
-JDK_WINDOWS_SHA256="35a8d018f420fb05fe7c2aa9933122896ca50bd23dbd373e90d8e2f3897c4e92"
+JDK_WINDOWS_X64_URL="https://download.java.net/java/GA/jdk12/GPL/openjdk-12_windows-x64_bin.zip"
+JDK_WINDOWS_X64_SHA256="35a8d018f420fb05fe7c2aa9933122896ca50bd23dbd373e90d8e2f3897c4e92"
 
 GRADLE_URL="https://services.gradle.org/distributions/gradle-5.6.2-bin.zip"
 GRADLE_SHA256="32fce6628848f799b0ad3205ae8db67d0d828c10ffe62b748a7c0d9f4a5d9ee0"
diff a/gradlew b/gradlew
--- a/gradlew
+++ b/gradlew
@@ -79,72 +79,75 @@
     mkdir -p "${DIRECTORY}"
     unzip "${FILENAME}" -d "${DIRECTORY}" > /dev/null
 }
 
 DIR=$(dirname $0)
+ARCH=$(uname -m)
 OS=$(uname)
 
 . $(dirname "${0}")/deps.env
-case "${OS}" in
-    Linux )
-        JDK_URL="${JDK_LINUX_URL}"
-        JDK_SHA256="${JDK_LINUX_SHA256}"
-        ;;
-    Darwin )
-        JDK_URL="${JDK_MACOS_URL}"
-        JDK_SHA256="${JDK_MACOS_SHA256}"
-        ;;
-    CYGWIN_NT* )
-        JDK_URL="${JDK_WINDOWS_URL}"
-        JDK_SHA256="${JDK_WINDOWS_SHA256}"
-        ;;
-    *)
-        echo "error: unknown operating system ${OS}"
-        exit 1
-        ;;
-esac
-
-JDK_FILENAME="${DIR}/.jdk/$(basename ${JDK_URL})"
-if [ "${OS}" = "Linux" -o "${OS}" = "Darwin" ]; then
-    JDK_DIR="${DIR}/.jdk/$(basename -s '.tar.gz' ${JDK_URL})"
-else
-    JDK_DIR="${DIR}/.jdk/$(basename -s '.zip' ${JDK_URL})"
+if [ "${ARCH}" = "x86_64" ]; then
+    case "${OS}" in
+        Linux )
+            JDK_URL="${JDK_LINUX_X64_URL}"
+            JDK_SHA256="${JDK_LINUX_X64_SHA256}"
+            ;;
+        Darwin )
+            JDK_URL="${JDK_MACOS_X64_URL}"
+            JDK_SHA256="${JDK_MACOS_X64_SHA256}"
+            ;;
+        CYGWIN_NT* )
+            JDK_URL="${JDK_WINDOWS_X64_URL}"
+            JDK_SHA256="${JDK_WINDOWS_X64_SHA256}"
+            ;;
+    esac
 fi
 
-if [ ! -d "${JDK_DIR}" ]; then
-    mkdir -p ${DIR}/.jdk
-    if [ ! -f "${JDK_FILENAME}" ]; then
-        if [ -f "${JDK_URL}" ]; then
-            echo "Copying JDK..."
-            cp "${JDK_URL}" "${JDK_FILENAME}"
+if [ ! -z "${JDK_URL}" ]; then
+    JDK_FILENAME="${DIR}/.jdk/$(basename ${JDK_URL})"
+    if [ "${OS}" = "Linux" -o "${OS}" = "Darwin" ]; then
+        JDK_DIR="${DIR}/.jdk/$(basename -s '.tar.gz' ${JDK_URL})"
+    else
+        JDK_DIR="${DIR}/.jdk/$(basename -s '.zip' ${JDK_URL})"
+    fi
+
+    if [ ! -d "${JDK_DIR}" ]; then
+        mkdir -p ${DIR}/.jdk
+        if [ ! -f "${JDK_FILENAME}" ]; then
+            if [ -f "${JDK_URL}" ]; then
+                echo "Copying JDK..."
+                cp "${JDK_URL}" "${JDK_FILENAME}"
+            else
+                echo "Downloading JDK..."
+                download ${JDK_URL} "${JDK_FILENAME}"
+                checksum "${JDK_FILENAME}" ${JDK_SHA256}
+            fi
+        fi
+        echo "Extracting JDK..."
+        if [ "${OS}" = "Linux" -o "${OS}" = "Darwin" ]; then
+            extract_tar "${JDK_FILENAME}" "${JDK_DIR}"
         else
-            echo "Downloading JDK..."
-            download ${JDK_URL} "${JDK_FILENAME}"
-            checksum "${JDK_FILENAME}" ${JDK_SHA256}
+            extract_zip "${JDK_FILENAME}" "${JDK_DIR}"
         fi
     fi
-    echo "Extracting JDK..."
-    if [ "${OS}" = "Linux" -o "${OS}" = "Darwin" ]; then
-        extract_tar "${JDK_FILENAME}" "${JDK_DIR}"
+
+    if [ "${OS}" = "Darwin" ]; then
+        EXECUTABLE_FILTER='-perm +111'
+        LAUNCHER='java'
+    elif [ "${OS}" = "Linux" ]; then
+        EXECUTABLE_FILTER='-executable'
+        LAUNCHER='java'
     else
-        extract_zip "${JDK_FILENAME}" "${JDK_DIR}"
+        LAUNCHER='java.exe'
     fi
-fi
 
-if [ "${OS}" = "Darwin" ]; then
-    EXECUTABLE_FILTER='-perm +111'
-    LAUNCHER='java'
-elif [ "${OS}" = "Linux" ]; then
-    EXECUTABLE_FILTER='-executable'
-    LAUNCHER='java'
+    JAVA_LAUNCHER=$(find "${JDK_DIR}" -type f ${EXECUTABLE_FILTER} | grep ".*/bin/${LAUNCHER}$")
+    export JAVA_HOME="$(dirname $(dirname ${JAVA_LAUNCHER}))"
 else
-    LAUNCHER='java.exe'
+    JAVA_LAUNCHER="java"
 fi
 
-JAVA_LAUNCHER=$(find "${JDK_DIR}" -type f ${EXECUTABLE_FILTER} | grep ".*/bin/${LAUNCHER}$")
-export JAVA_HOME="$(dirname $(dirname ${JAVA_LAUNCHER}))"
-
 GRADLE_FILENAME="${DIR}/.gradle/$(basename ${GRADLE_URL})"
 GRADLE_DIR="${DIR}/.gradle/$(basename -s '.zip' ${GRADLE_URL})"
 
 if [ ! -d "${GRADLE_DIR}" ]; then
     mkdir -p "${DIR}/.gradle"
@@ -153,11 +156,15 @@
         download ${GRADLE_URL} "${GRADLE_FILENAME}"
     fi
     checksum ${GRADLE_FILENAME} ${GRADLE_SHA256}
     echo "Extracting Gradle..."
     if [ "${OS}" = "Linux" -o "${OS}" = "Darwin" ]; then
-        "${JAVA_LAUNCHER}" "${DIR}"/Unzip.java "${GRADLE_FILENAME}" "${GRADLE_DIR}"
+        if exists unzip; then
+            extract_zip "${GRADLE_FILENAME}" "${GRADLE_DIR}"
+        else
+            "${JAVA_LAUNCHER}" "${DIR}"/Unzip.java "${GRADLE_FILENAME}" "${GRADLE_DIR}"
+        fi
     else
         extract_zip "${GRADLE_FILENAME}" "${GRADLE_DIR}"
     fi
 fi
 
diff a/gradlew.bat b/gradlew.bat
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -19,18 +19,18 @@
 rem Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 rem or visit www.oracle.com if you need additional information or have any
 rem questions.
 
 for /f "tokens=1,2 delims==" %%A in (deps.env) do (set %%A=%%~B)
-for /f %%i in ("%JDK_WINDOWS_URL%") do set JDK_WINDOWS_DIR=%%~ni
+for /f %%i in ("%JDK_WINDOWS_X64_URL%") do set JDK_WINDOWS_DIR=%%~ni
 for /f %%i in ("%GRADLE_URL%") do set GRADLE_DIR=%%~ni
 
 if exist %~dp0\.jdk\%JDK_WINDOWS_DIR% goto gradle
 
 echo Downloading JDK...
 mkdir %~dp0\.jdk\temp
-curl -L %JDK_WINDOWS_URL% -o %JDK_WINDOWS_DIR%.zip
+curl -L %JDK_WINDOWS_X64_URL% -o %JDK_WINDOWS_DIR%.zip
 move %JDK_WINDOWS_DIR%.zip %~dp0\.jdk\
 for /f "tokens=*" %%i in ('@certutil -hashfile %~dp0/.jdk/%JDK_WINDOWS_DIR%.zip sha256 ^| %WINDIR%\System32\find /v "hash of file" ^| %WINDIR%\System32\find /v "CertUtil"') do set SHA256JDK=%%i
 if "%SHA256JDK%" == "%JDK_WINDOWS_SHA256%" (goto extractJdk)
 echo Invalid SHA256 for JDK detected (%SHA256JDK%)
 goto done
