From 5e5f8638c00b63027f192f5ae85b1f4e61cc54c4 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Wed, 3 Jan 2024 15:32:39 +0530 Subject: [PATCH] fix: generate gradle wrapper with WrapperGenerator in 'generateGradleWrapper' task --- build-logic/ide/build.gradle.kts | 1 + .../plugins/AndroidIDEAssetsPlugin.kt | 12 +- .../plugins/tasks/GradleVersionResolver.java | 118 +++++ ...rTask.kt => GradleWrapperGeneratorTask.kt} | 51 +- .../plugins/tasks/IDEWrapperGenerator.java | 474 ++++++++++++++++++ .../tasks/internal/WrapperDefaults.java | 37 ++ .../tasks/internal/WrapperGenerator.java | 138 +++++ 7 files changed, 802 insertions(+), 29 deletions(-) create mode 100644 build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/GradleVersionResolver.java rename build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/{GradleWrapperBuilderTask.kt => GradleWrapperGeneratorTask.kt} (61%) create mode 100644 build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/IDEWrapperGenerator.java create mode 100644 build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/internal/WrapperDefaults.java create mode 100644 build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/internal/WrapperGenerator.java diff --git a/build-logic/ide/build.gradle.kts b/build-logic/ide/build.gradle.kts index 84029bba5..82139f2f0 100644 --- a/build-logic/ide/build.gradle.kts +++ b/build-logic/ide/build.gradle.kts @@ -44,5 +44,6 @@ dependencies { implementation(libs.common.jkotlin) implementation(libs.common.antlr4) + implementation(libs.google.gson) implementation(libs.google.java.format) } diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/AndroidIDEAssetsPlugin.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/AndroidIDEAssetsPlugin.kt index 3b1c72ce6..4077919bb 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/AndroidIDEAssetsPlugin.kt +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/AndroidIDEAssetsPlugin.kt @@ -23,9 +23,9 @@ import com.android.build.api.variant.AndroidComponentsExtension import com.android.build.gradle.BaseExtension import com.itsaky.androidide.plugins.tasks.AddAndroidJarToAssetsTask import com.itsaky.androidide.plugins.tasks.AddFileToAssetsTask -import com.itsaky.androidide.plugins.tasks.SetupAapt2Task import com.itsaky.androidide.plugins.tasks.GenerateInitScriptTask -import com.itsaky.androidide.plugins.tasks.GradleWrapperBuilderTask +import com.itsaky.androidide.plugins.tasks.GradleWrapperGeneratorTask +import com.itsaky.androidide.plugins.tasks.SetupAapt2Task import com.itsaky.androidide.plugins.util.SdkUtils.getAndroidJar import downloadVersion import org.gradle.api.Plugin @@ -51,8 +51,8 @@ class AndroidIDEAssetsPlugin : Plugin { override fun apply(target: Project) { target.run { - val wrapperBuilderTaskProvider = tasks.register("generateGradleWrapper", - GradleWrapperBuilderTask::class.java + val wrapperGeneratorTaskProvider = tasks.register("generateGradleWrapper", + GradleWrapperGeneratorTask::class.java ) val androidComponentsExtension = extensions.getByType(AndroidComponentsExtension::class.java) @@ -76,8 +76,8 @@ class AndroidIDEAssetsPlugin : Plugin { val variantNameCapitalized = variant.name.capitalized() - variant.sources.assets?.addGeneratedSourceDirectory(wrapperBuilderTaskProvider, - GradleWrapperBuilderTask::outputDirectory) + variant.sources.assets?.addGeneratedSourceDirectory(wrapperGeneratorTaskProvider, + GradleWrapperGeneratorTask::outputDirectory) variant.sources.assets?.addGeneratedSourceDirectory(addAndroidJarTaskProvider, AddAndroidJarToAssetsTask::outputDirectory) diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/GradleVersionResolver.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/GradleVersionResolver.java new file mode 100644 index 000000000..57f129664 --- /dev/null +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/GradleVersionResolver.java @@ -0,0 +1,118 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.itsaky.androidide.plugins.tasks; + +import static org.gradle.api.tasks.wrapper.internal.DefaultWrapperVersionsResources.LATEST; +import static org.gradle.api.tasks.wrapper.internal.DefaultWrapperVersionsResources.NIGHTLY; +import static org.gradle.api.tasks.wrapper.internal.DefaultWrapperVersionsResources.RELEASE_CANDIDATE; +import static org.gradle.api.tasks.wrapper.internal.DefaultWrapperVersionsResources.RELEASE_NIGHTLY; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.Map; +import org.gradle.api.GradleException; +import org.gradle.api.resources.TextResource; +import org.gradle.api.tasks.wrapper.internal.DefaultWrapperVersionsResources; +import org.gradle.util.GradleVersion; + +class GradleVersionResolver { + + private TextResource latest; + private TextResource releaseCandidate; + private TextResource nightly; + private TextResource releaseNightly; + + private GradleVersion gradleVersion; + private String gradleVersionString = GradleVersion.current().getVersion(); + + void setTextResources(TextResource latest, TextResource releaseCandidate, TextResource nightly, + TextResource releaseNightly) { + this.latest = latest; + this.releaseCandidate = releaseCandidate; + this.nightly = nightly; + this.releaseNightly = releaseNightly; + } + + String resolve(String version) { + if (version == null) { + return GradleVersion.current().getVersion(); + } + switch (version) { + case LATEST: + return getVersion(latest.asString(), version); + case NIGHTLY: + return getVersion(nightly.asString(), version); + case RELEASE_NIGHTLY: + return getVersion(releaseNightly.asString(), version); + case RELEASE_CANDIDATE: + return getVersion(releaseCandidate.asString(), version); + default: + return version; + } + } + + static String getVersion(String json, String placeHolder) { + Type type = new TypeToken>() { + }.getType(); + Map map = new Gson().fromJson(json, type); + String version = map.get("version"); + if (version == null) { + throw new GradleException( + "There is currently no version information available for '" + placeHolder + "'."); + } + return version; + } + + static boolean isPlaceHolder(String version) { + return DefaultWrapperVersionsResources.PLACE_HOLDERS.contains(version); + } + + GradleVersion getGradleVersion() { + if (gradleVersion == null) { + gradleVersion = GradleVersion.version(resolve(gradleVersionString)); + } + return gradleVersion; + } + + void setGradleVersionString(String gradleVersionString) { + if (!isPlaceHolder(gradleVersionString)) { + this.gradleVersion = GradleVersion.version(gradleVersionString); + } + if (this.gradleVersionString != gradleVersionString) { + this.gradleVersionString = gradleVersionString; + this.gradleVersion = null; + } + } +} \ No newline at end of file diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/GradleWrapperBuilderTask.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/GradleWrapperGeneratorTask.kt similarity index 61% rename from build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/GradleWrapperBuilderTask.kt rename to build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/GradleWrapperGeneratorTask.kt index 89d630245..8f551d48b 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/GradleWrapperBuilderTask.kt +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/GradleWrapperGeneratorTask.kt @@ -21,8 +21,8 @@ import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.wrapper.Wrapper import java.io.File -import java.nio.charset.StandardCharsets import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream @@ -31,7 +31,7 @@ import java.util.zip.ZipOutputStream * * @author Akash Yadav */ -abstract class GradleWrapperBuilderTask : DefaultTask() { +abstract class GradleWrapperGeneratorTask : DefaultTask() { /** * The output directory. @@ -42,14 +42,6 @@ abstract class GradleWrapperBuilderTask : DefaultTask() { companion object { private const val GRADLE_VERSION = "7.4.2" - - private val GRADLE_PROPERTIES_CONTENTS = """ - distributionBase=GRADLE_USER_HOME - distributionPath=wrapper/dists - distributionUrl=https\://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip - zipStoreBase=GRADLE_USER_HOME - zipStorePath=wrapper/dists - """.trimIndent() } @TaskAction @@ -63,24 +55,37 @@ abstract class GradleWrapperBuilderTask : DefaultTask() { destFile.delete() } + val stagingDir = File(outputDirectory, "staging") + if (stagingDir.exists()) { + stagingDir.deleteRecursively() + } + stagingDir.mkdirs() + + // Generate the files + val generator = IDEWrapperGenerator() + generator.jarFile = File(stagingDir, "gradle/wrapper/gradle-wrapper.jar") + generator.scriptFile = File(stagingDir, "gradlew") + generator.gradleVersion = GRADLE_VERSION + generator.distributionType = Wrapper.DistributionType.BIN + + generator.generate(project) + + // Archive all generated files ZipOutputStream(destFile.outputStream().buffered()).use { zipOut -> - - // Add static files - for (file in arrayOf("gradlew", "gradlew.bat", "gradle/wrapper/gradle-wrapper.jar")) { - val fileIn = File(project.rootProject.rootDir, file) - val entry = ZipEntry(file) - zipOut.putNextEntry(entry) - fileIn.inputStream().buffered().use { fileInStream -> - fileInStream.transferTo(zipOut) + stagingDir.walk(direction = FileWalkDirection.TOP_DOWN) + .filter { it.isFile } + .forEach { file -> + val entry = ZipEntry(file.relativeTo(stagingDir).path) + zipOut.putNextEntry(entry) + file.inputStream().buffered().use { fileInStream -> + fileInStream.transferTo(zipOut) + } } - } - - zipOut.putNextEntry(ZipEntry("gradle/wrapper/gradle-wrapper.properties")) - zipOut.write(GRADLE_PROPERTIES_CONTENTS.toByteArray(charset = StandardCharsets.UTF_8)) zipOut.flush() } - destFile.setLastModified(System.currentTimeMillis()) + // finally, delete the staging directory + stagingDir.deleteRecursively() } } \ No newline at end of file diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/IDEWrapperGenerator.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/IDEWrapperGenerator.java new file mode 100644 index 000000000..c9b69228a --- /dev/null +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/IDEWrapperGenerator.java @@ -0,0 +1,474 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.itsaky.androidide.plugins.tasks; + +import com.itsaky.androidide.plugins.tasks.internal.WrapperDefaults; +import com.itsaky.androidide.plugins.tasks.internal.WrapperGenerator; +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; +import javax.annotation.Nullable; +import javax.inject.Inject; +import kotlin.io.FilesKt; +import org.gradle.api.GradleException; +import org.gradle.api.Incubating; +import org.gradle.api.Project; +import org.gradle.api.UncheckedIOException; +import org.gradle.api.internal.file.FileLookup; +import org.gradle.api.internal.file.FileOperations; +import org.gradle.api.internal.file.FileResolver; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.options.Option; +import org.gradle.api.tasks.options.OptionValues; +import org.gradle.api.tasks.wrapper.Wrapper.DistributionType; +import org.gradle.api.tasks.wrapper.Wrapper.PathBase; +import org.gradle.api.tasks.wrapper.WrapperVersionsResources; +import org.gradle.api.tasks.wrapper.internal.DefaultWrapperVersionsResources; +import org.gradle.util.GradleVersion; +import org.gradle.util.internal.GUtil; +import org.gradle.util.internal.WrapperDistributionUrlConverter; +import org.gradle.wrapper.Download; +import org.gradle.wrapper.Logger; +import org.gradle.wrapper.WrapperExecutor; + +/** + *

Generates scripts (for *nix and windows) which allow you to build your project with Gradle, + * without having to install Gradle. + * + *

When a user executes a wrapper script the first time, the script downloads and installs the + * appropriate Gradle distribution and runs the build against this downloaded distribution. Any + * installed Gradle distribution is ignored when using the wrapper scripts. + * + *

The scripts generated by this task are intended to be committed to your version control + * system. This task also generates a small {@code gradle-wrapper.jar} bootstrap JAR file and + * properties file which should also be committed to your VCS. The scripts delegates to this JAR. + */ +public class IDEWrapperGenerator { + + public static final String DEFAULT_DISTRIBUTION_PARENT_NAME = WrapperDefaults.DISTRIBUTION_PATH; + + private File scriptFile = new File(WrapperDefaults.SCRIPT_PATH); + private File jarFile = new File(WrapperDefaults.JAR_FILE_PATH); + private String distributionPath = DEFAULT_DISTRIBUTION_PARENT_NAME; + private PathBase distributionBase = WrapperDefaults.DISTRIBUTION_BASE; + private String distributionUrl; + private String distributionSha256Sum; + private final GradleVersionResolver gradleVersionResolver = new GradleVersionResolver(); + private DistributionType distributionType = WrapperDefaults.DISTRIBUTION_TYPE; + private String archivePath = WrapperDefaults.ARCHIVE_PATH; + private PathBase archiveBase = WrapperDefaults.ARCHIVE_BASE; + private final int networkTimeout = WrapperDefaults.NETWORK_TIMEOUT; + private boolean distributionUrlConfigured = false; + + void generate(Project project) { + final var isOffline = project.getGradle().getStartParameter().isOffline(); + File jarFileDestination = getJarFile(); + File unixScript = getScriptFile(); + File propertiesFile = getPropertiesFile(); + Properties existingProperties = + propertiesFile.exists() ? GUtil.loadProperties(propertiesFile) : null; + + checkProperties(existingProperties); + validateDistributionUrl(propertiesFile.getParentFile(), isOffline); + + WrapperGenerator.generate(archiveBase, archivePath, distributionBase, distributionPath, + getDistributionSha256Sum(existingProperties), propertiesFile, jarFileDestination, + FilesKt.relativeTo(jarFileDestination, unixScript.getParentFile()).getPath(), unixScript, getBatchScript(), getDistributionUrl(), + getValidateDistributionUrl(), networkTimeout); + } + + private void checkProperties(Properties existingProperties) { + String checksumProperty = existingProperties != null ? existingProperties.getProperty( + WrapperExecutor.DISTRIBUTION_SHA_256_SUM, null) : null; + + if (!isCurrentVersion() && distributionSha256Sum == null && checksumProperty != null) { + throw new GradleException( + "gradle-wrapper.properties contains distributionSha256Sum property, but the wrapper configuration does not have one. Specify one in the wrapper task configuration or with the --gradle-distribution-sha256-sum task option"); + } + } + + private static final String DISTRIBUTION_URL_EXCEPTION_MESSAGE = "Test of distribution url %s failed. Please check the values set with --gradle-distribution-url and --gradle-version."; + + private void validateDistributionUrl(File uriRoot, boolean isOffline) { + if (distributionUrlConfigured && getValidateDistributionUrl()) { + String url = getDistributionUrl(); + URI uri = getDistributionUri(uriRoot, url); + if (uri.getScheme().equals("file")) { + if (!Files.exists(Paths.get(uri).toAbsolutePath())) { + throw new UncheckedIOException(String.format(DISTRIBUTION_URL_EXCEPTION_MESSAGE, url)); + } + } else if (uri.getScheme().startsWith("http") && !isOffline) { + try { + new Download(new Logger(true), "gradlew", Download.UNKNOWN_VERSION).sendHeadRequest(uri); + } catch (Exception e) { + throw new UncheckedIOException(String.format(DISTRIBUTION_URL_EXCEPTION_MESSAGE, url), e); + } + } + } + } + + private static URI getDistributionUri(File uriRoot, String url) { + try { + return WrapperDistributionUrlConverter.convertDistributionUrl(url, uriRoot); + } catch (URISyntaxException e) { + throw new GradleException("Distribution URL String cannot be parsed: " + url, e); + } + } + + private String getDistributionSha256Sum(Properties existingProperties) { + if (distributionSha256Sum != null) { + return distributionSha256Sum; + } else if (isCurrentVersion() && existingProperties != null) { + return existingProperties.getProperty(WrapperExecutor.DISTRIBUTION_SHA_256_SUM, null); + } else { + return null; + } + } + + private boolean isCurrentVersion() { + return GradleVersion.current().equals(gradleVersionResolver.getGradleVersion()); + } + + /** + * Returns the file to write the wrapper script to. + */ + + public File getScriptFile() { + return scriptFile; + } + + /** + * The file to write the wrapper script to. + * + * @since 4.0 + */ + public void setScriptFile(File scriptFile) { + this.scriptFile = scriptFile; + } + + /** + * Returns the file to write the wrapper batch script to. + */ + + public File getBatchScript() { + return WrapperGenerator.getBatchScript(getScriptFile()); + } + + /** + * Returns the file to write the wrapper jar file to. + */ + + public File getJarFile() { + return this.jarFile; + } + + /** + * The file to write the wrapper jar file to. + * + * @since 4.0 + */ + public void setJarFile(File jarFile) { + this.jarFile = jarFile; + } + + /** + * Returns the file to write the wrapper properties to. + */ + + public File getPropertiesFile() { + return WrapperGenerator.getPropertiesFile(getJarFile()); + } + + /** + * Returns the path where the gradle distributions needed by the wrapper are unzipped. The path is + * relative to the distribution base directory + * + * @see #setDistributionPath(String) + */ + + public String getDistributionPath() { + return distributionPath; + } + + /** + * Sets the path where the gradle distributions needed by the wrapper are unzipped. The path is + * relative to the distribution base directory + * + * @see #setDistributionPath(String) + */ + public void setDistributionPath(String distributionPath) { + this.distributionPath = distributionPath; + } + + /** + * Set Wrapper versions resources. + * + * @since 8.1 + */ + @Incubating + public void setWrapperVersionsResources(WrapperVersionsResources wrapperVersionsResources) { + DefaultWrapperVersionsResources defaultWrapperVersionsResources = (DefaultWrapperVersionsResources) wrapperVersionsResources; + gradleVersionResolver.setTextResources(defaultWrapperVersionsResources.getLatest(), + defaultWrapperVersionsResources.getReleaseCandidate(), + defaultWrapperVersionsResources.getNightly(), + defaultWrapperVersionsResources.getReleaseNightly()); + } + + /** + * Returns the gradle version for the wrapper. + * + * @throws GradleException if the label that can be provided via {@link #setGradleVersion(String)} + * can not be resolved at the moment. For example, there is not a + * `release-candidate` available at all times. + * @see #setGradleVersion(String) + */ + + public String getGradleVersion() { + return gradleVersionResolver.getGradleVersion().getVersion(); + } + + /** + * The version of the gradle distribution required by the wrapper. This is usually the same + * version of Gradle you use for building your project. The following labels are allowed to + * specify a version: {@code latest}, {@code release-candidate}, {@code nightly}, and + * {@code release-nightly} + * + *

The resulting distribution url is validated before it is written to the + * gradle-wrapper.properties file. + */ + @Option(option = "gradle-version", description = + "The version of the Gradle distribution required by the wrapper. " + + "The following labels are allowed: latest, release-candidate, nightly, and release-nightly.") + public void setGradleVersion(String gradleVersion) { + distributionUrlConfigured = true; + this.gradleVersionResolver.setGradleVersionString(gradleVersion); + } + + /** + * Returns the type of the Gradle distribution to be used by the wrapper. + * + * @see #setDistributionType(DistributionType) + */ + + public DistributionType getDistributionType() { + return distributionType; + } + + /** + * The type of the Gradle distribution to be used by the wrapper. By default, this is + * {@link DistributionType#BIN}, which is the binary-only Gradle distribution without + * documentation. + * + * @see DistributionType + */ + @Option(option = "distribution-type", description = "The type of the Gradle distribution to be used by the wrapper.") + public void setDistributionType(DistributionType distributionType) { + this.distributionType = distributionType; + } + + /** + * The list of available gradle distribution types. + */ + @OptionValues("distribution-type") + public List getAvailableDistributionTypes() { + return Arrays.asList(DistributionType.values()); + } + + /** + * The URL to download the gradle distribution from. + * + *

If not set, the download URL is the default for the specified {@link #getGradleVersion()}. + * + *

If {@link #getGradleVersion()} is not set, will return null. + * + *

The wrapper downloads a certain distribution only once and caches it. If your distribution + * base is the project, you might submit the distribution to your version control system. That way + * no download is necessary at all. This might be in particular interesting, if you provide a + * custom gradle snapshot to the wrapper, because you don't need to provide a download server + * then. + */ + + public String getDistributionUrl() { + if (distributionUrl != null) { + return distributionUrl; + } + + return WrapperGenerator.getDistributionUrl(gradleVersionResolver.getGradleVersion(), + distributionType); + } + + /** + * The URL to download the gradle distribution from. + * + *

If not set, the download URL is the default for the specified {@link #getGradleVersion()}. + * + *

If {@link #getGradleVersion()} is not set, will return null. + * + *

The wrapper downloads a certain distribution and caches it. If your distribution base is + * the project, you might submit the distribution to your version control system. That way no + * download is necessary at all. This might be in particular interesting, if you provide a custom + * gradle snapshot to the wrapper, because you don't need to provide a download server then. + * + *

The distribution url is validated before it is written to the gradle-wrapper.properties + * file. + */ + @Option(option = "gradle-distribution-url", description = "The URL to download the Gradle distribution from.") + public void setDistributionUrl(String url) { + distributionUrlConfigured = true; + this.distributionUrl = url; + } + + /** + * The SHA-256 hash sum of the gradle distribution. + * + *

If not set, the hash sum of the gradle distribution is not verified. + * + *

The wrapper allows for verification of the downloaded Gradle distribution via SHA-256 hash + * sum comparison. This increases security against targeted attacks by preventing a + * man-in-the-middle attacker from tampering with the downloaded Gradle distribution. + * + * @since 4.5 + */ + @Nullable + @Optional + + public String getDistributionSha256Sum() { + return distributionSha256Sum; + } + + /** + * The SHA-256 hash sum of the gradle distribution. + * + *

If not set, the hash sum of the gradle distribution is not verified. + * + *

The wrapper allows for verification of the downloaded Gradle distribution via SHA-256 hash + * sum comparison. This increases security against targeted attacks by preventing a + * man-in-the-middle attacker from tampering with the downloaded Gradle distribution. + * + * @since 4.5 + */ + @Option(option = "gradle-distribution-sha256-sum", description = "The SHA-256 hash sum of the gradle distribution.") + public void setDistributionSha256Sum(@Nullable String distributionSha256Sum) { + this.distributionSha256Sum = distributionSha256Sum; + } + + /** + * The distribution base specifies whether the unpacked wrapper distribution should be stored in + * the project or in the gradle user home dir. + */ + + public PathBase getDistributionBase() { + return distributionBase; + } + + /** + * The distribution base specifies whether the unpacked wrapper distribution should be stored in + * the project or in the gradle user home dir. + */ + public void setDistributionBase(PathBase distributionBase) { + this.distributionBase = distributionBase; + } + + /** + * Returns the path where the gradle distributions archive should be saved (i.e. the parent dir). + * The path is relative to the archive base directory. + */ + + public String getArchivePath() { + return archivePath; + } + + /** + * Set's the path where the gradle distributions archive should be saved (i.e. the parent dir). + * The path is relative to the parent dir specified with {@link #getArchiveBase()}. + */ + public void setArchivePath(String archivePath) { + this.archivePath = archivePath; + } + + /** + * The archive base specifies whether the unpacked wrapper distribution should be stored in the + * project or in the gradle user home dir. + */ + + public PathBase getArchiveBase() { + return archiveBase; + } + + /** + * The archive base specifies whether the unpacked wrapper distribution should be stored in the + * project or in the gradle user home dir. + */ + public void setArchiveBase(PathBase archiveBase) { + this.archiveBase = archiveBase; + } + + /** + * The network timeout specifies how many ms to wait for when the wrapper is performing network + * operations, such as downloading the wrapper jar. + * + * @since 7.6 + */ + + @Incubating + @Optional + @Option(option = "network-timeout", description = "Timeout in ms to use when the wrapper is performing network operations.") + public Integer getNetworkTimeout() { + return networkTimeout; + } + + /** + * Indicates if this task will validate the distribution url that has been configured. + * + * @return whether this task will validate the distribution url + * @since 8.2 + */ + @Incubating + + @Option(option = "validate-url", description = "Sets task to validate the configured distribution url.") + public Boolean getValidateDistributionUrl() { + return true; + } + + @Inject + protected FileLookup getFileLookup() { + throw new UnsupportedOperationException(); + } +} + diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/internal/WrapperDefaults.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/internal/WrapperDefaults.java new file mode 100644 index 000000000..9b07f648a --- /dev/null +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/internal/WrapperDefaults.java @@ -0,0 +1,37 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.plugins.tasks.internal; + +import org.gradle.api.NonNullApi; +import org.gradle.api.tasks.wrapper.Wrapper; +import org.gradle.wrapper.Install; + +@NonNullApi +public class WrapperDefaults { + public static final String SCRIPT_PATH = "gradlew"; + public static final String JAR_FILE_PATH = "gradle/wrapper/gradle-wrapper.jar"; + public static final Wrapper.DistributionType DISTRIBUTION_TYPE = Wrapper.DistributionType.BIN; + + public static final String DISTRIBUTION_PATH = Install.DEFAULT_DISTRIBUTION_PATH; + public static final Wrapper.PathBase DISTRIBUTION_BASE = Wrapper.PathBase.GRADLE_USER_HOME; + public static final String ARCHIVE_PATH = DISTRIBUTION_PATH; + public static final Wrapper.PathBase ARCHIVE_BASE = DISTRIBUTION_BASE; + + public static final int NETWORK_TIMEOUT = 10000; + public static final boolean VALIDATE_DISTRIBUTION_URL = true; +} \ No newline at end of file diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/internal/WrapperGenerator.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/internal/WrapperGenerator.java new file mode 100644 index 000000000..4e3dd44eb --- /dev/null +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/internal/WrapperGenerator.java @@ -0,0 +1,138 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.plugins.tasks.internal; + +import com.google.common.collect.ImmutableList; +import com.google.common.io.ByteStreams; +import org.gradle.api.GradleException; +import org.gradle.api.NonNullApi; +import org.gradle.api.UncheckedIOException; +import org.gradle.api.internal.plugins.StartScriptGenerator; +import org.gradle.api.tasks.wrapper.Wrapper; +import org.gradle.api.tasks.wrapper.Wrapper.PathBase; +import org.gradle.internal.util.PropertiesUtils; +import org.gradle.util.GradleVersion; +import org.gradle.util.internal.DistributionLocator; +import org.gradle.util.internal.GFileUtils; +import org.gradle.wrapper.GradleWrapperMain; +import org.gradle.wrapper.WrapperExecutor; + +import javax.annotation.Nullable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.nio.file.Files; +import java.util.Locale; +import java.util.Properties; + +import static java.util.Collections.singletonList; + +@NonNullApi +public class WrapperGenerator { + + public static File getPropertiesFile(File jarFileDestination) { + return new File(jarFileDestination.getParentFile(), jarFileDestination.getName().replaceAll("\\.jar$", ".properties")); + } + + public static File getBatchScript(File scriptFile) { + return new File(scriptFile.getParentFile(), scriptFile.getName().replaceFirst("(\\.[^\\.]+)?$", ".bat")); + } + + public static String getDistributionUrl(GradleVersion gradleVersion, Wrapper.DistributionType distributionType) { + String distType = distributionType.name().toLowerCase(Locale.ENGLISH); + return new DistributionLocator().getDistributionFor(gradleVersion, distType).toASCIIString(); + } + + public static void generate( + PathBase archiveBase, String archivePath, + PathBase distributionBase, String distributionPath, + @Nullable String distributionSha256Sum, + File wrapperPropertiesOutputFile, + File wrapperJarOutputFile, String jarFileRelativePath, + File unixScript, File batchScript, + @Nullable String distributionUrl, + boolean validateDistributionUrl, + @Nullable Integer networkTimeout + ) { + writeProperties(wrapperPropertiesOutputFile, distributionUrl, distributionSha256Sum, distributionBase, distributionPath, archiveBase, archivePath, networkTimeout, validateDistributionUrl); + writeWrapperJar(wrapperJarOutputFile); + writeScripts(jarFileRelativePath, unixScript, batchScript); + } + + private static void writeProperties( + File propertiesFileDestination, + @Nullable String distributionUrl, + @Nullable String distributionSha256Sum, + PathBase distributionBase, + String distributionPath, + PathBase archiveBase, + String archivePath, + @Nullable Integer networkTimeout, + boolean validateDistributionUrl + ) { + Properties wrapperProperties = new Properties(); + wrapperProperties.put(WrapperExecutor.DISTRIBUTION_URL_PROPERTY, distributionUrl); + if (distributionSha256Sum != null) { + wrapperProperties.put(WrapperExecutor.DISTRIBUTION_SHA_256_SUM, distributionSha256Sum); + } + wrapperProperties.put(WrapperExecutor.DISTRIBUTION_BASE_PROPERTY, distributionBase.toString()); + wrapperProperties.put(WrapperExecutor.DISTRIBUTION_PATH_PROPERTY, distributionPath); + wrapperProperties.put(WrapperExecutor.ZIP_STORE_BASE_PROPERTY, archiveBase.toString()); + wrapperProperties.put(WrapperExecutor.ZIP_STORE_PATH_PROPERTY, archivePath); + if (networkTimeout != null) { + wrapperProperties.put(WrapperExecutor.NETWORK_TIMEOUT_PROPERTY, String.valueOf(networkTimeout)); + } + wrapperProperties.put(WrapperExecutor.VALIDATE_DISTRIBUTION_URL, String.valueOf(validateDistributionUrl)); + + GFileUtils.parentMkdirs(propertiesFileDestination); + try { + PropertiesUtils.store(wrapperProperties, propertiesFileDestination); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static void writeWrapperJar(File destination) { + URL jarFileSource = Wrapper.class.getResource("/gradle-wrapper.jar"); + if (jarFileSource == null) { + throw new GradleException("Cannot locate wrapper JAR resource."); + } + try (InputStream in = jarFileSource.openStream(); OutputStream out = Files.newOutputStream(destination.toPath())) { + ByteStreams.copy(in, out); + } catch (IOException e) { + throw new UncheckedIOException("Failed to write wrapper JAR to " + destination, e); + } + } + + private static void writeScripts(String jarFileRelativePath, File unixScript, File batchScript) { + StartScriptGenerator generator = new StartScriptGenerator(); + generator.setApplicationName("Gradle"); + generator.setMainClassName(GradleWrapperMain.class.getName()); + generator.setClasspath(singletonList(jarFileRelativePath)); + generator.setOptsEnvironmentVar("GRADLE_OPTS"); + generator.setExitEnvironmentVar("GRADLE_EXIT_CONSOLE"); + generator.setAppNameSystemProperty("org.gradle.appname"); + generator.setScriptRelPath(unixScript.getName()); + generator.setDefaultJvmOpts(ImmutableList.of("-Xmx64m", "-Xms64m")); + generator.generateUnixScript(unixScript); + generator.generateWindowsScript(batchScript); + } + +} \ No newline at end of file