diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-07-21 16:09:33 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-07-21 16:09:33 +0000 |
commit | 7bd99a740e903a6c16b0c40a5b34e0d5bf8b3e41 (patch) | |
tree | c908a4c8796c6ee71a7ab0b9ba203be1ca2099c3 | |
parent | 372c27be732a527dd8e51a53d8762a25515b4638 (diff) | |
parent | fcf558ee0f7bc7c6314de6079efa254474bb955c (diff) | |
download | gradle-recipes-studio-canary.tar.gz |
Snap for 8853699 from fcf558ee0f7bc7c6314de6079efa254474bb955c to studio-ee-releasestudio-2022.1.1-rc3studio-2022.1.1-canarystudio-2022.1.1-beta4studio-2022.1.1-beta2studio-2022.1.1studio-canarystudio-2022.1.1-canary
Change-Id: Iff6584118c3fe8d0281141e074a5a208ad4b378f
27 files changed, 1851 insertions, 0 deletions
diff --git a/convert-tool/.gitattributes b/convert-tool/.gitattributes new file mode 100644 index 0000000..00a51af --- /dev/null +++ b/convert-tool/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/convert-tool/.gitignore b/convert-tool/.gitignore new file mode 100644 index 0000000..0e13346 --- /dev/null +++ b/convert-tool/.gitignore @@ -0,0 +1,6 @@ +# Ignore Gradle project-specific cache directory +.gradle +.idea + +# Ignore Gradle build output directory +build diff --git a/convert-tool/app/build.gradle.kts b/convert-tool/app/build.gradle.kts new file mode 100644 index 0000000..89388d2 --- /dev/null +++ b/convert-tool/app/build.gradle.kts @@ -0,0 +1,50 @@ +// workaround for https://youtrack.jetbrains.com/issue/KTIJ-19369 +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + alias(libs.plugins.kotlin) + application +} + +dependencies { + // Align versions of all Kotlin components + implementation(platform(libs.kotlin.bom)) + implementation(libs.kotlin.stdlib) + + // app dependencies + implementation(libs.guava) + implementation(libs.kotlinx.cli) + implementation(libs.tomlj) + implementation(libs.semver) + implementation(libs.gradle.api) + runtimeOnly(libs.slf4j) + + // test dependencies + testImplementation(libs.kotlin.test) + testImplementation(libs.kotlin.test.junit) + testImplementation(libs.google.truth) +} + +application { + // Define the main class for the application. + mainClass.set("com.google.android.gradle_recipe.converter.ConvertToolKt") +} + +base { + archivesName.set("recipes-converter") +} + +tasks { + register("standaloneJar", Jar::class.java) { + archiveClassifier.set("all") + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + manifest { + attributes["Main-Class"] = "com.google.android.gradle_recipe.converter.ConvertToolKt" + } + from(configurations.runtimeClasspath.get() + .onEach { println("add from dependencies: ${it.name}") } + .map { if (it.isDirectory) it else zipTree(it) }) + val sourcesMain = sourceSets.main.get() + sourcesMain.allSource.forEach { println("add from sources: ${it.name}") } + from(sourcesMain.output) + } +}
\ No newline at end of file diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/ConvertTool.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/ConvertTool.kt new file mode 100644 index 0000000..1a59edf --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/ConvertTool.kt @@ -0,0 +1,109 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter + +import com.google.android.gradle_recipe.converter.converters.RecipeConverter +import com.google.android.gradle_recipe.converter.converters.RecursiveConverter +import com.google.android.gradle_recipe.converter.converters.convertStringToMode +import com.google.android.gradle_recipe.converter.validators.GithubPresubmitValidator +import com.google.android.gradle_recipe.converter.validators.InternalCIValidator +import com.google.android.gradle_recipe.converter.validators.WorkingCopyValidator +import kotlinx.cli.ArgParser +import kotlinx.cli.ArgType +import kotlinx.cli.default +import java.nio.file.Path + +/** + * The main entry to the Converter, parser the command line arguments, and calls + * the relevant classes to perform the work + */ +fun main(args: Array<String>) { + val parser = ArgParser("Gradle Recipe Converter") + + val action by parser.option( + ArgType.Choice(listOf("convert", "validate"), { it }), shortName = "al", description = "Recipe action" + ).default("convert") + + val overwrite by parser.option(ArgType.Boolean, shortName = "o", description = "Overwrite").default(false) + val source by parser.option(ArgType.String, shortName = "s", description = "Recipe source") + val sourceAll by parser.option(ArgType.String, shortName = "sa", description = "All recipe sources") + val destination by parser.option(ArgType.String, shortName = "d", description = "Destination folder") + val tmpFolder by parser.option(ArgType.String, shortName = "tf", description = "Temp folder") + val agpVersion by parser.option(ArgType.String, shortName = "a", description = "AGP version") + val repoLocation by parser.option(ArgType.String, shortName = "rl", description = "Repo location") + val gradleVersion by parser.option(ArgType.String, shortName = "gv", description = "Gradle version") + val gradlePath by parser.option(ArgType.String, shortName = "gp", description = "Gradle path") + val mode by parser.option(ArgType.String, shortName = "m", description = "Mode") + + parser.parse(args) + + if (action == "convert") { + if (source != null) { + RecipeConverter( + agpVersion = agpVersion, + repoLocation = repoLocation, + gradleVersion = gradleVersion, + gradlePath = gradlePath, + mode = convertStringToMode(mode), + overwrite = overwrite + ).convert( + source = Path.of(source ?: error("source must be specified")), + destination = Path.of(destination ?: error("destination must be specified")) + ) + } else { + RecursiveConverter( + agpVersion = agpVersion, + repoLocation = repoLocation, + gradleVersion = gradleVersion, + gradlePath = gradlePath, + overwrite = overwrite + ).convertAllRecipes( + sourceAll = Path.of(sourceAll ?: error("sourceAll must be specified")), + destination = Path.of(destination ?: error("destination must be specified")) + ) + } + } else if (action == "validate") { + if (agpVersion == null) { + if (mode != null) { + val cliMode = convertStringToMode(mode) + if (cliMode != RecipeConverter.Mode.COPY) { + error("The mode value should be either \"copy\" or nothing") + } + + val validator = WorkingCopyValidator() + validator.validate( + Path.of(source ?: error("Source can't be null")) + ) + } else { + val validator = GithubPresubmitValidator() + validator.validateAll( + Path.of(sourceAll ?: error("SourceAll can't be null")) + ) + } + } else { + val validator = InternalCIValidator( + agpVersion = agpVersion ?: error("agpVersion can't be null"), + repoLocation = repoLocation ?: error("repoLocation can't be null"), + gradlePath = gradlePath ?: error("gradlePath can't be null") + ) + validator.validate( + sourceAll = Path.of(sourceAll ?: error("sourceAll can't be null")), + tmpFolder = if (tmpFolder != null) Path.of(tmpFolder) else null + ) + } + } +} diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/Converter.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/Converter.kt new file mode 100644 index 0000000..fa8694c --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/Converter.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.converters + +import com.google.android.gradle_recipe.converter.recipe.Recipe +import java.nio.file.Path + +/** Interface for different converters. + * The objects are created and called from the RecipeConverter class, + * using a Template Method pattern. + */ +interface Converter { + + /** Can converter convert this recipe + */ + fun isConversionCompliant(recipe: Recipe): Boolean + + /** sets the recipe to convert, before the conversion + */ + fun setRecipe(recipe: Recipe) {} + + /** + * converts settings.gradle + */ + fun convertSettingsGradle(source: Path, target: Path) + + /** + * converts settings.gradle.kts ==> same as settings.gradle + */ + fun convertSettingsGradleKts(source: Path, target: Path) { + convertSettingsGradle(source, target) + } + + /** + * converts build.gradle + */ + fun convertBuildGradle(source: Path, target: Path) + + /** + * converts build.gradle.kts ==> same as build.gradle + */ + fun convertBuildGradleKts(source: Path, target: Path) { + convertBuildGradle(source, target) + } + + /** + * converts gradle.wrapper + */ + fun convertGradleWrapper(source: Path, target: Path) +}
\ No newline at end of file diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/ConverterUtils.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/ConverterUtils.kt new file mode 100644 index 0000000..9a598d9 --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/ConverterUtils.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.converters + +/** The functions in this file perform the conversion by adding and removing + * the placeholder data. The functions swap the placeholders + * ($AGP_VERSION, $AGP_REPOSITORY and $GRADLE_LOCATION) with actual + * values and commenting the relevant lines to use later + */ + +/** + * for build.gradle[.kts] , settings.gradle[.kts] ==> wraps $AGP_VERSION or $AGP_REPOSITORY + */ +fun wrapGradlePlaceholders(originalLines: List<String>, from: String, to: String): List<String> { + return wrapPlaceholders(originalLines, from, to, "// ") +} + +/** + * for build.gradle[.kts] , settings.gradle[.kts] ==> unwraps $AGP_VERSION or $AGP_REPOSITORY + */ +fun unwrapGradlePlaceholders(originalLines: List<String>): List<String> { + return unwrapPlaceholders(originalLines, "// ") +} + +/** + * for gradle.wrapper.properties ==> wraps $GRADLE_LOCATION + */ +fun wrapGradleWrapperPlaceholders(originalLines: List<String>, from: String, to: String): List<String> { + return wrapPlaceholders(originalLines, from, to, "# ") +} + +/** + * for gradle.wrapper.properties ==> unwraps $GRADLE_LOCATION + */ +fun unwrapGradleWrapperPlaceholders(originalLines: List<String>): List<String> { + return unwrapPlaceholders(originalLines, "# ") +} + +/** + * replaces placeholders with actual values, without keeping the placeholders + */ +fun replacePlaceHolderWithValue(originalLines: List<String>, placeHolder: String, value: String): List<String> { + val result = buildList { + for (line in originalLines) { + if (line.contains(placeHolder)) { + if (value.isNotEmpty()) { + add(line.replace(placeHolder, value)) + } + + } else { + add(line) + } + } + } + + return result +} + +/** + * wraps both gradle and properties with commentOut + */ +private fun wrapPlaceholders(originalLines: List<String>, from: String, to: String, commentOut: String): List<String> { + val result = buildList { + for (line in originalLines) { + if (line.contains(from)) { + add("$commentOut>>> WORKING_COPY >>>") + add("$commentOut$line") + + if (to.isNotEmpty()) { + val newLine: String = line.replace(from, to) + add(newLine) + } + + add("$commentOut<<< WORKING_COPY <<<") + } else { + add(line) + } + } + } + + return result +} + +/** + * unwraps both gradle and properties with commentOut + */ +private fun unwrapPlaceholders(originalLines: List<String>, commentOut: String): List<String> { + var insideWorkBlock = false + val result = buildList { + for (line in originalLines) { + if (line == "$commentOut>>> WORKING_COPY >>>") { + insideWorkBlock = true + } else if (insideWorkBlock) { + if (line != "$commentOut<<< WORKING_COPY <<<") { + if (line.startsWith("$commentOut")) { + add(line.substring(commentOut.length)) + } + + } else { + // we are in "<<< WORKING_COPY <<<", means finished the block + insideWorkBlock = false + } + } else { + add(line) + } + } + } + + return result +} diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/RecipeConverter.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/RecipeConverter.kt new file mode 100644 index 0000000..9fec3af --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/RecipeConverter.kt @@ -0,0 +1,164 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.converters + +import com.google.android.gradle_recipe.converter.recipe.Recipe +import com.google.android.gradle_recipe.converter.recipe.RecipeMetadataParser +import java.io.IOException +import java.lang.System.err +import java.nio.file.* +import java.nio.file.attribute.BasicFileAttributes +import kotlin.io.path.exists +import kotlin.io.path.isDirectory + +val agpToGradleVersions = mapOf( + "3.6" to "5.6", + "4.0" to "6.1.1", + "4.1" to "6.5", + "4.2" to "6.7.1", + "7.0" to "7.0", + "7.1" to "7.2", + "7.2" to "7.3.3", + "7.4" to "7.3.3" +) + +fun convertStringToMode(modeFromString: String?): RecipeConverter.Mode { + return if (modeFromString != null) { + RecipeConverter.Mode.valueOf(modeFromString.toString().uppercase()) + } else { + RecipeConverter.Mode.RELEASE + } +} + +data class ConversionResult(val recipe: Recipe, var isConversionSuccessful: Boolean) + +/** + * Converts the individual recipe, calculation the conversion mode by input parameters + */ +class RecipeConverter( + val agpVersion: String?, + repoLocation: String?, + gradleVersion: String?, + gradlePath: String?, + mode: Mode, + private val overwrite: Boolean, +) { + private val converter: Converter + + enum class Mode { + RELEASE, COPY, SOURCE + } + + init { + converter = when (mode) { + Mode.COPY -> { + WorkingCopyConverter() + } + + Mode.SOURCE -> { + SourceConverter() + } + + Mode.RELEASE -> { + ReleaseConverter( + agpVersion = agpVersion ?: error("Must specify the AGP version for release"), + gradleVersion = gradleVersion, + repoLocation = repoLocation, + gradlePath = gradlePath + ) + } + } + } + + @Throws(IOException::class) + fun convert(source: Path, destination: Path): ConversionResult { + if (!source.isDirectory()) { + error("the source $source folder is not a directory") + } + + if (destination.exists() && !isEmpty(destination)) { + if (!overwrite) { + error("the destination $destination folder is not empty, call converter with --overwrite to overwrite it") + } else { + destination.toFile().deleteRecursively() + } + } + + val metadataParser = RecipeMetadataParser(source) + val recipe = Recipe( + minAgpVersion = metadataParser.minAgpVersion, + maxAgpVersion = metadataParser.maxAgpVersion, + tasks = metadataParser.tasks, + keywords = metadataParser.indexKeywords + ) + + val result = ConversionResult(recipe, false) + + if (converter.isConversionCompliant(recipe)) { + converter.setRecipe(recipe) + result.isConversionSuccessful = true + + Files.walkFileTree(source, object : SimpleFileVisitor<Path>() { + @Throws(IOException::class) + override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult { + Files.createDirectories(destination.resolve(source.relativize(dir))) + return FileVisitResult.CONTINUE + } + + @Throws(IOException::class) + override fun visitFile(sourceFile: Path, attrs: BasicFileAttributes): FileVisitResult { + val fileName = sourceFile.fileName.toString() + val destinationFile = destination.resolve(source.relativize(sourceFile)) + + when (fileName) { + "build.gradle" -> { + converter.convertBuildGradle(sourceFile, destinationFile) + } + "build.gradle.kts" -> { + converter.convertBuildGradleKts(sourceFile, destinationFile) + } + "settings.gradle" -> { + converter.convertSettingsGradle(sourceFile, destinationFile) + } + "settings.gradle.kts" -> { + converter.convertSettingsGradleKts(sourceFile, destinationFile) + } + "gradle-wrapper.properties" -> { + converter.convertGradleWrapper(sourceFile, destinationFile) + } + else -> { + Files.copy(sourceFile, destinationFile, StandardCopyOption.REPLACE_EXISTING) + } + } + + return FileVisitResult.CONTINUE + } + }) + } else { + err.println("Couldn't convert $source due to AGP version compliance ") + } + + return result + } + + @Throws(IOException::class) + fun isEmpty(path: Path): Boolean { + if (Files.isDirectory(path)) { + Files.list(path).use { entries -> return !entries.findFirst().isPresent } + } + return false + } +} diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/RecursiveConverter.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/RecursiveConverter.kt new file mode 100644 index 0000000..87e60f4 --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/RecursiveConverter.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.converters + +import com.google.android.gradle_recipe.converter.converters.RecipeConverter.Mode +import com.google.android.gradle_recipe.converter.recipe.visitRecipes +import java.io.File +import java.io.IOException +import java.nio.file.Path +import java.util.* +import kotlin.io.path.exists + +const val INDEX_METADATA_FILE = "README.md" + +/* Recursive converter converts recipes from source of truth mode + to release mode + */ +class RecursiveConverter( + private val agpVersion: String?, + private var repoLocation: String?, + var gradleVersion: String?, + var gradlePath: String?, + private var overwrite: Boolean, +) { + + private val keywordsToRecipePaths = mutableMapOf<String, MutableList<Path>>() + + @Throws(IOException::class) + fun convertAllRecipes(sourceAll: Path, destination: Path) { + if (!sourceAll.exists()) { + error("the source $sourceAll folder doesn't exist") + } + + val recipeConverter = RecipeConverter( + agpVersion = agpVersion, + repoLocation = repoLocation, + gradleVersion = gradleVersion, + gradlePath = gradlePath, + mode = Mode.RELEASE, + overwrite = overwrite + ) + + visitRecipes(sourceAll) { recipeFolder: Path -> + val recipeRelativeName = sourceAll.relativize(recipeFolder) + val currentRecipeDestination = destination.resolve(recipeRelativeName) + val conversionResult = recipeConverter.convert( + recipeFolder, + currentRecipeDestination + ) + + if (conversionResult.isConversionSuccessful) { + for (keyword in conversionResult.recipe.keywords) { + val list = keywordsToRecipePaths.computeIfAbsent(keyword) { mutableListOf() } + list.add(recipeRelativeName) + } + } + } + + writeRecipesIndexFile(keywordsToRecipePaths.toSortedMap(), destination) + } + + private fun writeRecipesIndexFile( + keywordsToRecipePaths: MutableMap<String, MutableList<Path>>, + destination: Path, + ) { + val builder = StringBuilder() + val commaDelimiter = ", " + builder.appendLine("# Recipes Index") + + keywordsToRecipePaths.keys.forEach { indexKeyword -> + builder.appendLine("* $indexKeyword - ") + val joiner = StringJoiner(commaDelimiter) + + keywordsToRecipePaths[indexKeyword]?.forEach { recipeRelativePath -> + val line = + "[$recipeRelativePath]($recipeRelativePath)" + joiner.add(line) + } + + builder.appendLine(joiner.toString()) + } + + File( + destination.resolve(INDEX_METADATA_FILE).toUri() + ).writeText(builder.toString()) + } +}
\ No newline at end of file diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/ReleaseConverter.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/ReleaseConverter.kt new file mode 100644 index 0000000..c0d1b12 --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/ReleaseConverter.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.converters + +import com.google.android.gradle_recipe.converter.recipe.Recipe +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.writeLines + +/** This mode is for a recipe that has no placeholders. + * This is for releases ando/or tests + */ +class ReleaseConverter( + private val agpVersion: String, + gradleVersion: String?, + repoLocation: String?, + gradlePath: String?, +) : Converter { + + private var pathToGradle: String = "" + private var pathToRepo: String = "" + + init { + + if (gradleVersion != null) { + pathToGradle = "https\\://services.gradle.org/distributions/gradle-" + + "$gradleVersion-bin.zip" + } else { + pathToRepo = repoLocation ?: error("must specify path to repo") + pathToGradle = gradlePath ?: error("must specify path to Gradle") + } + } + + override fun isConversionCompliant(recipe: Recipe): Boolean { + return recipe.isCompliantWithAgp(agpVersion) + } + + override fun convertBuildGradle(source: Path, target: Path) { + val originalLines = Files.readAllLines(source) + val resultLines: List<String> = replacePlaceHolderWithValue( + originalLines, + "\$AGP_VERSION", + "\"$agpVersion\"" + ) + + target.writeLines(resultLines, Charsets.UTF_8) + } + + override fun convertSettingsGradle(source: Path, target: Path) { + val originalLines = Files.readAllLines(source) + val resultLines: List<String> = replacePlaceHolderWithValue( + originalLines, "\$AGP_REPOSITORY", "$pathToRepo" + ) + + target.writeLines(resultLines, Charsets.UTF_8) + } + + override fun convertGradleWrapper(source: Path, target: Path) { + val originalLines = Files.readAllLines(source) + val resultLines: List<String> = replacePlaceHolderWithValue( + originalLines, "\$GRADLE_LOCATION", "$pathToGradle" + ) + + target.writeLines(resultLines, Charsets.UTF_8) + } +}
\ No newline at end of file diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/SourceConverter.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/SourceConverter.kt new file mode 100644 index 0000000..2eb1213 --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/SourceConverter.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.converters + +import com.google.android.gradle_recipe.converter.recipe.Recipe +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.writeLines + +/** This mode has the placeholders ($AGP_VERSION etc') and + * this is how we store the recipes in the dev branch + */ +class SourceConverter : Converter { + override fun isConversionCompliant(recipe: Recipe): Boolean { + return true + } + + override fun convertSettingsGradle(source: Path, target: Path) { + val originalLines = Files.readAllLines(source) + val resultLines: List<String> = unwrapGradlePlaceholders(originalLines) + + target.writeLines(resultLines, Charsets.UTF_8) + } + + override fun convertBuildGradle(source: Path, target: Path) { + val originalLines = Files.readAllLines(source) + val resultLines: List<String> = unwrapGradlePlaceholders(originalLines) + + target.writeLines(resultLines, Charsets.UTF_8) + } + + override fun convertGradleWrapper(source: Path, target: Path) { + val originalLines = Files.readAllLines(source) + val resultLines: List<String> = unwrapGradleWrapperPlaceholders(originalLines) + + target.writeLines(resultLines, Charsets.UTF_8) + } +}
\ No newline at end of file diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/WorkingCopyConverter.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/WorkingCopyConverter.kt new file mode 100644 index 0000000..5738afd --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/converters/WorkingCopyConverter.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.converters + +import com.google.android.gradle_recipe.converter.recipe.Recipe +import com.google.android.gradle_recipe.converter.recipe.getAgpVersionMajorMinorFrom +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.writeLines + +/** + * This is the working copy where the recipe has static values for $AGP_VERSION, etc... + * but markers to revert them back to placeholders. + */ +class WorkingCopyConverter : Converter { + private var recipe: Recipe? = null + override fun isConversionCompliant(recipe: Recipe): Boolean { + return true + } + + override fun setRecipe(recipe: Recipe) { + this.recipe = recipe + } + + override fun convertBuildGradle(source: Path, target: Path) { + val agpVersion = recipe?.minAgpVersion ?: error("min Agp version is badly specified in the metadata") + + val originalLines = Files.readAllLines(source) + val resultLines: List<String> = wrapGradlePlaceholders( + originalLines, "\$AGP_VERSION", "\"$agpVersion\"" + ) + target.writeLines(resultLines, Charsets.UTF_8) + } + + override fun convertSettingsGradle(source: Path, target: Path) { + val originalLines = Files.readAllLines(source) + val resultLines: List<String> = wrapGradlePlaceholders( + originalLines, "\$AGP_REPOSITORY", "" + ) + + target.writeLines(resultLines, Charsets.UTF_8) + } + + override fun convertGradleWrapper(source: Path, target: Path) { + // building the line + // distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip + val agpVersion = recipe?.minAgpVersion ?: error("min Agp version is badly specified in the metadata") + + val agpVersionMajorMinor = getAgpVersionMajorMinorFrom(agpVersion) + val gradleVersion = agpToGradleVersions[agpVersionMajorMinor] + ?: error("Can't deduce the gradle version from the recipe metadata") + + val originalLines = Files.readAllLines(source) + val resultLines: List<String> = wrapGradleWrapperPlaceholders( + originalLines, + "\$GRADLE_LOCATION", + "https\\://services.gradle.org/distributions/gradle-$gradleVersion-bin.zip" + ) + target.writeLines(resultLines, Charsets.UTF_8) + } +}
\ No newline at end of file diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/recipe/Recipe.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/recipe/Recipe.kt new file mode 100644 index 0000000..d36e27f --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/recipe/Recipe.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.recipe + +import com.github.rising3.semver.SemVer +import java.io.File +import java.nio.file.Path + +/** A recipe metadata model + * + */ +class Recipe( + val minAgpVersion: String, + val maxAgpVersion: String?, + val tasks: List<String>, + val keywords: List<String>, +) { + + fun isCompliantWithAgp(agpVersion: String): Boolean { + val min = minAgpVersion + val max = maxAgpVersion + + return if (max != null) { + SemVer.parse(agpVersion) >= SemVer.parse(min) && SemVer.parse(agpVersion) <= SemVer.parse(max) + } else { + // when maxAgpVersion is not specified + SemVer.parse(agpVersion) >= SemVer.parse(min) + } + } +} + +fun getAgpVersionMajorMinorFrom(agpVersion: String): String { + return SemVer.parse(agpVersion).major.toString() + "." + SemVer.parse(agpVersion).minor.toString() +} + +fun isRecipeFolder(folder: Path) = File(folder.toFile(), RECIPE_METADATA_FILE).exists() diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/recipe/RecipeMetadataParser.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/recipe/RecipeMetadataParser.kt new file mode 100644 index 0000000..a80c897 --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/recipe/RecipeMetadataParser.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.recipe + +import org.tomlj.Toml +import org.tomlj.TomlParseResult +import java.io.File +import java.nio.file.Path + +const val RECIPE_METADATA_FILE = "recipe_metadata.toml" + +/** Recipe metadata parser, that reads and parses the metadata file + * from the recipe folder + */ +class RecipeMetadataParser(recipeFolder: Path) { + private var parseResult: TomlParseResult = Toml.parse(File(recipeFolder.toFile(), RECIPE_METADATA_FILE).toPath()) + val minAgpVersion: String + val maxAgpVersion: String? + val tasks: List<String> + val indexKeywords: List<String> + + init { + if (parseResult.hasErrors()) { + System.err.println("Recipes error/s at $recipeFolder") + parseResult.errors().forEach { error -> System.err.println(error.toString()) } + throw IllegalArgumentException("Badly formatted recipe_metadata.toml file") + } + + val minAgpVersionFromMetadata = parseResult.getString("agpVersion.min") + + if (minAgpVersionFromMetadata != null) { + minAgpVersion = minAgpVersionFromMetadata + } else { + throw IllegalArgumentException("the minimal AGP version must be specified in the recipe metadata file") + } + + maxAgpVersion = parseResult.getString("agpVersion.max") + + val tasksList = parseResult.getArray("gradleTasks.tasks")?.toList() + tasks = tasksList?.let { list -> + list.map { + it as String + } + } ?: emptyList() + + val indexKeywordsList = parseResult.getArray("indexMetadata.index")?.toList() + indexKeywords = indexKeywordsList?.let { list -> + list.map { + it as String + } + } ?: emptyList() + } +}
\ No newline at end of file diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/recipe/RecipeUtils.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/recipe/RecipeUtils.kt new file mode 100644 index 0000000..06038cc --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/recipe/RecipeUtils.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.recipe + +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.exists +import kotlin.io.path.isHidden + +/** Visitor function, recursively traverses rootFolder with recipes and + * activates recipeCallback for each found recipe. + */ +fun visitRecipes(rootFolder: Path, recipeCallback: (Path) -> Unit) { + if (!rootFolder.exists()) { + error("the source $rootFolder folder doesn't exist") + } + + Files.walk(rootFolder).filter { currentPath: Path -> + Files.isDirectory(currentPath) + && currentPath != rootFolder + && !currentPath.isHidden() + && isRecipeFolder(currentPath) + }.forEach { recipeFolder: Path -> + recipeCallback(recipeFolder) + } +}
\ No newline at end of file diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/GithubPresubmitValidator.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/GithubPresubmitValidator.kt new file mode 100644 index 0000000..28a702d --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/GithubPresubmitValidator.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.validators + +import com.google.android.gradle_recipe.converter.recipe.visitRecipes +import java.nio.file.Path + +/** This will test all recipes found against both they min and + * current/max version of AGP + */ +class GithubPresubmitValidator { + + fun validateAll(sourceAll: Path) { + val recipeValidator = MinMaxCurrentAgpValidator(sourceAll) + visitRecipes(sourceAll) { recipeFolder: Path -> + recipeValidator.validate(recipeFolder) + } + } +}
\ No newline at end of file diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/GradleTasksExecutor.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/GradleTasksExecutor.kt new file mode 100644 index 0000000..62d1b33 --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/GradleTasksExecutor.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.validators + +import org.gradle.tooling.GradleConnector +import org.gradle.tooling.ProjectConnection +import java.lang.System.err +import java.nio.file.Path + +/** Executes Gradle tasks via GradleConnector API + */ +class GradleTasksExecutor(projectDir: Path) { + private val connector: GradleConnector = GradleConnector.newConnector() + + init { + connector.forProjectDirectory(projectDir.toFile()) + } + + fun executeTasks(tasks: List<String>) { + tasks.forEach { task -> + println("$task ") + executeTask(task) + } + } + + private fun executeTask(vararg tasks: String?) { + try { + val connection: ProjectConnection = connector.connect() + val build: org.gradle.tooling.BuildLauncher = connection.newBuild() + build.forTasks(*tasks) + build.run() + connection.close() + } catch (e: RuntimeException) { + err.println("Task: ${tasks[0]} failed with, ${e.message}") + } + } +} diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/InternalCIValidator.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/InternalCIValidator.kt new file mode 100644 index 0000000..f9ae242 --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/InternalCIValidator.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.validators + +import com.google.android.gradle_recipe.converter.converters.RecipeConverter +import com.google.android.gradle_recipe.converter.converters.RecipeConverter.Mode +import com.google.android.gradle_recipe.converter.recipe.visitRecipes +import java.io.IOException +import java.nio.file.Path +import kotlin.io.path.createTempDirectory + +/** Validates al the recipes against a very specific version + * of AGP, and Gradle, in specific locations + */ +class InternalCIValidator( + private val agpVersion: String, + private val repoLocation: String, + private val gradlePath: String, +) { + @Throws(IOException::class) + fun validate(sourceAll: Path, tmpFolder: Path?) { + + val converter = RecipeConverter( + agpVersion = agpVersion, + repoLocation = repoLocation, + gradleVersion = null, + gradlePath = gradlePath, + mode = Mode.RELEASE, + overwrite = true + ) + + visitRecipes(sourceAll) { recipeFolder: Path -> + val destinationFolder: Path + + if (tmpFolder != null) { + destinationFolder = tmpFolder + } else { + destinationFolder = createTempDirectory() + destinationFolder.toFile().deleteOnExit() + } + + val conversionResult = converter.convert( + source = recipeFolder, destination = destinationFolder + ) + + if (conversionResult.isConversionSuccessful) { + println("Validating: $destinationFolder with AGP: $agpVersion and Gradle: $gradlePath") + val tasksExecutor = GradleTasksExecutor(destinationFolder) + tasksExecutor.executeTasks(conversionResult.recipe.tasks) + } + } + } +} diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/MinMaxCurrentAgpValidator.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/MinMaxCurrentAgpValidator.kt new file mode 100644 index 0000000..dc86afa --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/MinMaxCurrentAgpValidator.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.validators + +import com.google.android.gradle_recipe.converter.converters.RecipeConverter +import com.google.android.gradle_recipe.converter.converters.RecipeConverter.Mode +import com.google.android.gradle_recipe.converter.converters.agpToGradleVersions +import com.google.android.gradle_recipe.converter.recipe.RecipeMetadataParser +import com.google.android.gradle_recipe.converter.recipe.getAgpVersionMajorMinorFrom +import java.nio.file.Path +import kotlin.io.path.createTempDirectory + +/** Validates recipe from source mode and with currentAgpFileLocation, by calling validation + * with min and current/max AGP versions + */ +class MinMaxCurrentAgpValidator(private val currentAGPFileLocation: Path) { + + private val currentAGPVersionFile = "currentAgpVersion.txt" + + fun validate(source: Path) { + val recipeMetadataParser = RecipeMetadataParser(source) + val minAgpVersion = recipeMetadataParser.minAgpVersion + val maxAgpVersion = recipeMetadataParser.maxAgpVersion + + validateRecipeFromSource(source, minAgpVersion) + + if (maxAgpVersion != null) { + validateRecipeFromSource(source, maxAgpVersion) + } else { + var currentAgpVersion: String? = null + val currentAgpFile = currentAGPFileLocation.resolve(currentAGPVersionFile).toFile() + + if (currentAgpFile.exists()) { + currentAgpVersion = currentAgpFile.readText() + } + + if (currentAgpVersion != null) { + validateRecipeFromSource(source, currentAgpVersion) + } else { + error( + "Neither maxAgp version was defined in the metadata, " + + "nor currentAgp defined in $currentAGPVersionFile " + + "defined in $source ==> thus validated only with minAgp" + ) + } + } + } + + private fun validateRecipeFromSource( + from: Path, + agpVersion: String, + ) { + val gradleVersion = agpToGradleVersions[getAgpVersionMajorMinorFrom(agpVersion)] + + val recipeConverter = RecipeConverter( + agpVersion = agpVersion, + gradleVersion = gradleVersion, + repoLocation = null, + gradlePath = null, + mode = Mode.RELEASE, + overwrite = true + ) + + val destinationFolder = createTempDirectory() + destinationFolder.toFile().deleteOnExit() + + val conversionResult = recipeConverter.convert( + source = from, destination = destinationFolder + ) + + if (conversionResult.isConversionSuccessful) { + println("Validating: $destinationFolder with AGP: $agpVersion and Gradle: $gradleVersion") + val tasksExecutor = GradleTasksExecutor(destinationFolder) + tasksExecutor.executeTasks(conversionResult.recipe.tasks) + } + } +}
\ No newline at end of file diff --git a/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/WorkingCopyValidator.kt b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/WorkingCopyValidator.kt new file mode 100644 index 0000000..a916a47 --- /dev/null +++ b/convert-tool/app/src/main/kotlin/com/google/android/gradle_recipe/converter/validators/WorkingCopyValidator.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.validators + +import com.google.android.gradle_recipe.converter.converters.RecipeConverter +import com.google.android.gradle_recipe.converter.converters.RecipeConverter.Mode +import java.nio.file.Path +import kotlin.io.path.createTempDirectory + +/** This will convert the recipe back to sources (using a temp folder), + * and then create 2 release versions, using the min and current/max versions of + * AGP and tests against these. + */ +class WorkingCopyValidator { + + fun validate(recipeSource: Path) { + val recipeValidator = MinMaxCurrentAgpValidator(recipeSource) + recipeValidator.validate(convertToSourceOfTruth(recipeSource)) + } + + private fun convertToSourceOfTruth(from: Path): Path { + val sourceOfTruthTempDirectory: Path = createTempDirectory() + sourceOfTruthTempDirectory.toFile().deleteOnExit() + + val convertToSourceTruth = RecipeConverter( + agpVersion = null, + gradleVersion = null, + repoLocation = null, + gradlePath = null, + mode = Mode.SOURCE, + overwrite = true + ) + convertToSourceTruth.convert( + source = from, destination = sourceOfTruthTempDirectory + ) + + return sourceOfTruthTempDirectory + } +} diff --git a/convert-tool/app/src/test/kotlin/com/google/android/gradle_recipe/converter/AGPVersionsTest.kt b/convert-tool/app/src/test/kotlin/com/google/android/gradle_recipe/converter/AGPVersionsTest.kt new file mode 100644 index 0000000..5b11302 --- /dev/null +++ b/convert-tool/app/src/test/kotlin/com/google/android/gradle_recipe/converter/AGPVersionsTest.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter + +import com.github.rising3.semver.SemVer +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class AGPVersionsTest { + @Test + fun alphaBeta() = + assertThat(SemVer.parse("7.1.0-alpha") < SemVer.parse("7.1.0-beta5")).isTrue() + + @Test + fun betaRc() = assertThat(SemVer.parse("7.1.0-beta") < SemVer.parse("7.1.0-rc5")).isTrue() + + @Test + fun rc1Rc2() = + assertThat(SemVer.parse("7.1.0-rc1") < SemVer.parse("7.1.0-rc2")).isTrue() + + @Test + fun rcFinal() = assertThat(SemVer.parse("7.1.0-rc1") < SemVer.parse("7.1.0")).isTrue() + + @Test + fun finalFinal() = assertThat(SemVer.parse("7.1.0") < SemVer.parse("8.1.0")).isTrue() +}
\ No newline at end of file diff --git a/convert-tool/app/src/test/kotlin/com/google/android/gradle_recipe/converter/converters/ConverterUtilsKtTest.kt b/convert-tool/app/src/test/kotlin/com/google/android/gradle_recipe/converter/converters/ConverterUtilsKtTest.kt new file mode 100644 index 0000000..951238d --- /dev/null +++ b/convert-tool/app/src/test/kotlin/com/google/android/gradle_recipe/converter/converters/ConverterUtilsKtTest.kt @@ -0,0 +1,166 @@ +/* + * Copyright 2022 Google, Inc. + * + * 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.google.android.gradle_recipe.converter.converters + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class ConverterUtilsKtTest { + + // build.gradle + private val buildGradleSource = listOf( + "id 'com.android.application' version \$AGP_VERSION apply false", + "id 'com.android.library' version \$AGP_VERSION apply false", + "id 'org.jetbrains.kotlin.android' version '1.6.10' apply false" + ) + + private val buildGradleWorkingCopy = listOf( + "// >>> WORKING_COPY >>>", + "// id 'com.android.application' version \$AGP_VERSION apply false", + "id 'com.android.application' version \"7.4.4\" apply false", + "// <<< WORKING_COPY <<<", + "// >>> WORKING_COPY >>>", + "// id 'com.android.library' version \$AGP_VERSION apply false", + "id 'com.android.library' version \"7.4.4\" apply false", + "// <<< WORKING_COPY <<<", + "id 'org.jetbrains.kotlin.android' version '1.6.10' apply false", + ) + + @Test + fun testBuildGradleRelease() { + val result = wrapGradlePlaceholders(buildGradleSource, "\$AGP_VERSION", "\"7.4.4\"") + assertThat(result).isEqualTo(buildGradleWorkingCopy) + } + + @Test + fun testBuildGradleSource() { + val result = unwrapGradlePlaceholders(buildGradleWorkingCopy) + assertThat(result).isEqualTo(buildGradleSource) + } + + // Tests for settings.gradle + private val settingsGradleSource = """ +pluginManagement { + repositories { +${'$'}AGP_REPOSITORY + gradlePluginPortal() + google() + mavenCentral() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { +${'$'}AGP_REPOSITORY + google() + mavenCentral() + } +}""" + + private val settingsGradleTest = """ +pluginManagement { + repositories { +// >>> WORKING_COPY >>> +// ${'$'}AGP_REPOSITORY +// <<< WORKING_COPY <<< + gradlePluginPortal() + google() + mavenCentral() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { +// >>> WORKING_COPY >>> +// ${'$'}AGP_REPOSITORY +// <<< WORKING_COPY <<< + google() + mavenCentral() + } +}""" + + private val settingsGradleRelease = """ +pluginManagement { + repositories { +// >>> WORKING_COPY >>> +// ${'$'}AGP_REPOSITORY +..\..\private-repo\ +// <<< WORKING_COPY <<< + gradlePluginPortal() + google() + mavenCentral() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { +// >>> WORKING_COPY >>> +// ${'$'}AGP_REPOSITORY +..\..\private-repo\ +// <<< WORKING_COPY <<< + google() + mavenCentral() + } +}""" + + @Test + fun testSettingsGradleWorkingCopy() { + val result = wrapGradlePlaceholders(settingsGradleSource.lines(), "\$AGP_REPOSITORY", "") + assertThat(result).isEqualTo(settingsGradleTest.lines()) + } + + @Test + fun testSettingsGradleSource() { + val result = unwrapGradlePlaceholders(settingsGradleTest.lines()) + assertThat(result).isEqualTo(settingsGradleSource.lines()) + } + + @Test + fun testSettingsGradleRelease() { + val result = wrapGradlePlaceholders(settingsGradleSource.lines(), "\$AGP_REPOSITORY", "..\\..\\private-repo\\") + assertThat(result).isEqualTo(settingsGradleRelease.lines()) + } + + // gradle wrapper tests + private val gradleWrapperSource = listOf( + "distributionUrl=\$GRADLE_LOCATION" + ) + + private val gradleWrapperRelease = listOf( + "# >>> WORKING_COPY >>>", + "# distributionUrl=\$GRADLE_LOCATION", + "distributionUrl=../../../tools/external/gradle/gradle-7.4-bin.zip", + "# <<< WORKING_COPY <<<", + ) + + @Test + fun testGradleWrapperRelease() { + val result = + wrapGradleWrapperPlaceholders( + gradleWrapperSource, + "\$GRADLE_LOCATION", + "../../../tools/external/gradle/gradle-7.4-bin.zip" + ) + assertThat(result).isEqualTo(gradleWrapperRelease) + } + + @Test + fun testGradleWrapperSource() { + val result = unwrapGradleWrapperPlaceholders(gradleWrapperRelease) + assertThat(gradleWrapperSource).isEqualTo(result) + } +}
\ No newline at end of file diff --git a/convert-tool/gradle/libs.versions.toml b/convert-tool/gradle/libs.versions.toml new file mode 100644 index 0000000..ca49019 --- /dev/null +++ b/convert-tool/gradle/libs.versions.toml @@ -0,0 +1,25 @@ +[versions] +kotlin = "1.7.10" +guava = "30.1.1-jre" +kotlinx-cli = "0.3.4" +tomlj = "1.0.0" +semver = "0.3.1" +gradle-api = "7.3-20210825160000+0000" +slf4j = "1.7.10" +truth = "1.1.3" + +[libraries] +kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" } +kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } +guava = { module = "com.google.guava:guava", version.ref = "guava" } +kotlinx-cli = { module = "org.jetbrains.kotlinx:kotlinx-cli", version.ref = "kotlinx-cli" } +tomlj = { module = "org.tomlj:tomlj", version.ref = "tomlj" } +semver = { module = "com.github.rising3:semver", version.ref = "semver" } +gradle-api = { module = "org.gradle:gradle-tooling-api", version.ref = "gradle-api" } +slf4j = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } +google-truth = { module = "com.google.truth:truth", version.ref = "truth" } + +[plugins] +kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
\ No newline at end of file diff --git a/convert-tool/gradle/wrapper/gradle-wrapper.jar b/convert-tool/gradle/wrapper/gradle-wrapper.jar Binary files differnew file mode 100644 index 0000000..7454180 --- /dev/null +++ b/convert-tool/gradle/wrapper/gradle-wrapper.jar diff --git a/convert-tool/gradle/wrapper/gradle-wrapper.properties b/convert-tool/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..aa991fc --- /dev/null +++ b/convert-tool/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/convert-tool/gradlew b/convert-tool/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/convert-tool/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/convert-tool/gradlew.bat b/convert-tool/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/convert-tool/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/convert-tool/settings.gradle.kts b/convert-tool/settings.gradle.kts new file mode 100644 index 0000000..07d09cb --- /dev/null +++ b/convert-tool/settings.gradle.kts @@ -0,0 +1,9 @@ +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenCentral() + } +} + +rootProject.name = "convert-tool" +include("app")
\ No newline at end of file |