From 8f8d599a49ffe052642426e43b065b54d453852b Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Wed, 7 Sep 2022 08:11:16 +0530 Subject: [PATCH] Moved IDE tasks in custom plugins --- build.gradle | 172 +----------------- buildSrc/build.gradle.kts | 12 +- .../java/com/itsaky/androidide/Versions.kt | 12 +- .../androidide/plugins/AndroidIDEPlugin.kt | 47 +++++ .../plugins/GoogleJavaFormatPlugin.kt | 153 ++++++++++++++++ .../plugins/TranslationCheckerPlugin.kt | 110 +++++++++++ 6 files changed, 325 insertions(+), 181 deletions(-) create mode 100644 buildSrc/src/main/java/com/itsaky/androidide/plugins/AndroidIDEPlugin.kt create mode 100644 buildSrc/src/main/java/com/itsaky/androidide/plugins/GoogleJavaFormatPlugin.kt create mode 100644 buildSrc/src/main/java/com/itsaky/androidide/plugins/TranslationCheckerPlugin.kt diff --git a/build.gradle b/build.gradle index 37fef069a..fc745f978 100755 --- a/build.gradle +++ b/build.gradle @@ -15,10 +15,10 @@ * along with AndroidIDE. If not, see . */ - import com.google.googlejavaformat.java.Formatter import com.google.googlejavaformat.java.JavaFormatterOptions import com.itsaky.androidide.Versions +import com.itsaky.androidide.plugins.AndroidIDEPlugin import java.nio.charset.StandardCharsets import java.nio.file.Files @@ -67,7 +67,6 @@ buildscript { dependencies { classpath classpaths.agp classpath classpaths.kotlin_gradle_plugin - classpath classpaths.google_java_format classpath classpaths.oss_licenses classpath google.guava_jre } @@ -75,14 +74,7 @@ buildscript { subprojects { apply plugin: 'kotlin-kapt' - - afterEvaluate { - tasks.register ("checkTranslations", task -> { - doLast { - checkTranslations (project) - } - }) - } + apply plugin: AndroidIDEPlugin } allprojects { @@ -116,172 +108,12 @@ allprojects { } } -private void checkTranslations (Project project) { - def php = new File ("/usr/bin/php") - if (!php.exists ()) { - project.logger.lifecycle ("'${php.absolutePath}' not found. Skipping translation check.") - return - } - - def resDir = project.file ("src/main/res") - def strings = new File (resDir, "values/strings.xml") - def reportDir = new File (project.rootProject.buildDir, "translation-reports") - reportDir.delete () - - if (!resDir.exists () || !strings.exists ()) { - project.logger.info ("Default strings.xml file does not exist for project '${project.name}'") - return - } - - def translationDirs = resDir.listFiles ((FileFilter) (file -> { - return file.isDirectory () && file.getName ().startsWith ("values-") - })) - - for (def dir : translationDirs) { - final var translation = new File (dir, "strings.xml") - if (!translation.exists ()) { - project.logger.info ("No translation file specifed for '${dir.name}'. Skipping..") - return - } - - def out = new File (reportDir, "${project.path.replace (':', '/')}/${dir.name}.txt") - if (!out.parentFile.exists ()) { - out.parentFile.mkdirs () - } - - if (out.exists ()) { - out.delete () - } - - out.createNewFile () - - def result = exec { - ignoreExitValue true - standardOutput new FileOutputStream (out) - commandLine "${php.absolutePath}", - "${project.rootProject.file (".tools/strings-check.php")}", - "${strings.absolutePath}", - "${translation.absolutePath}" - } - - if (result.getExitValue () == 0) { - out.delete () - } else { - project.logger.lifecycle ("Translation report for '${project.path}/${dir.name}' is written to '${out.absolutePath}'") - } - } -} task clean (type: Delete) { delete rootProject.buildDir } -Collection walkForSources (Path dir) { - try { - - if (!Files.exists (dir)) { - return Collections.emptySet () - } - - return Files.walk (dir) - .filter (path -> !Files.isDirectory (path)) - .filter (path -> Files.isReadable (path) && Files.isWritable (path)) - .map (path -> path.toFile ()) - .map (file -> file.absolutePath) - .filter (path -> path.endsWith (".java")) - .filter (path -> !path.endsWith ("_template.java")) - .collect (Collectors.toSet ()) - } catch (Throwable error) { - getLogger ().error ("Could not walk directory for java sources: ${dir.toString ()}", error) - return Collections.emptySet () - } -} - -boolean doJavaFormat (Formatter formatter, String path) { - try { - final def file = Paths.get (path) - final def content = Files.readAllBytes (file) - final def utf8Decoded = new String (content, StandardCharsets.UTF_8.name ()) - final def formatted = formatter.formatSource (utf8Decoded) - if (utf8Decoded == formatted) { - getLogger ().info ("{}: UP-TO-DATE", file) - return true - } - - byte[] utf8Encoded = formatted.getBytes (StandardCharsets.UTF_8.name ()) - Files.write (file, utf8Encoded) - println ("${path}: Formatted successfully!") - return true - } catch (Throwable th) { - getLogger ().error ("Failed to format ${path}", th) - return false - } -} - task formatJavaSources () { doLast { - def jarName = "google-java-format-1.15.0-all-deps" - def jar = rootProject.file (".tools/${jarName}.jar") - - if (!jar.exists ()) { - getLogger ().info ("google-java-format JAR file not found. Skipping format task...") - return - } - - boolean formattingChanges = false - rootProject.subprojects.forEach (sub -> { - def sources = walkForSources (sub.file ("src/main/java").toPath ()) - sources.addAll (walkForSources (sub.file ("src/test/java").toPath ())) - - final def formatter = new Formatter ( - JavaFormatterOptions - .builder () - .style (JavaFormatterOptions.Style.GOOGLE) - .formatJavadoc (true) - .build () - ) - - for (def path : sources) { - if (doJavaFormat (formatter, path)) { - formattingChanges = true - } - } - }) - - if (!formattingChanges) { - getLogger ().info ("No formatting changes.") - return - } - - // ---------------- Stage Changes ------------------- - def out = new ByteArrayOutputStream () - def exit = rootProject.exec { - ignoreExitValue true - standardOutput out - errorOutput standardOutput - commandLine "git", "add", "." - } - - if (exit.exitValue != 0) { - getLogger ().error ("Unable to stage changes. Process terminated with exit code ${exit.exitValue}") - getLogger ().error (out.toString ()) - return - } - - // ----------------- Commit Changes -------------------- - out = new ByteArrayOutputStream () - exit = rootProject.exec { - ignoreExitValue true - standardOutput out - errorOutput standardOutput - commandLine "git", "commit", "-m", "[Gradle] Format java source code" - } - - if (exit.exitValue == 0) { - getLogger ().info ("Changes committed successfully.") - } else { - getLogger ().error ("Failed to commit changes. Process terminated with exit code ${exit.exitValue}") - getLogger ().error (out.toString ()) - } } } \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 12102b61a..2d19116a0 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -20,11 +20,13 @@ plugins { } repositories { - google() - gradlePluginPortal() - mavenCentral() + google() + gradlePluginPortal() + mavenCentral() } dependencies { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") -} \ No newline at end of file + implementation(gradleApi()) + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation("com.google.googlejavaformat:google-java-format:1.15.0") +} diff --git a/buildSrc/src/main/java/com/itsaky/androidide/Versions.kt b/buildSrc/src/main/java/com/itsaky/androidide/Versions.kt index d2196a094..501156bfb 100644 --- a/buildSrc/src/main/java/com/itsaky/androidide/Versions.kt +++ b/buildSrc/src/main/java/com/itsaky/androidide/Versions.kt @@ -25,19 +25,19 @@ package com.itsaky.androidide object Versions { /** The compile SDK version. */ - val compileSdk = 32 + const val compileSdk = 32 /** The build tools version. */ - val buildTools = "31.0.0" + const val buildTools = "31.0.0" /** The minimum SDK version. */ - val minSdk = 26 + const val minSdk = 26 /** The target SDK version. */ - val targetSdk = 28 + const val targetSdk = 28 /** The version code. */ - val versionCode = 212 + const val versionCode = 212 /** * The version name for the IDE. When in CI build, includes the [releaseVersionName], branch name @@ -53,5 +53,5 @@ object Versions { } /** The release version name. */ - val releaseVersionName = "2.1.2-beta" + const val releaseVersionName = "2.1.2-beta" } diff --git a/buildSrc/src/main/java/com/itsaky/androidide/plugins/AndroidIDEPlugin.kt b/buildSrc/src/main/java/com/itsaky/androidide/plugins/AndroidIDEPlugin.kt new file mode 100644 index 000000000..08fcb4a4b --- /dev/null +++ b/buildSrc/src/main/java/com/itsaky/androidide/plugins/AndroidIDEPlugin.kt @@ -0,0 +1,47 @@ +/* + * 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 + +import org.gradle.api.Plugin +import org.gradle.api.Project + +/** + * Manages plugins applied on the IDE's project modules. + * + * @author Akash Yadav + */ +class AndroidIDEPlugin : Plugin { + + companion object { + private const val APP_PLUGIN = "com.android.application" + private const val LIB_PLUGIN = "com.android.library" + } + + override fun apply(target: Project) { + target.afterEvaluate { + if (target.plugins.hasPlugin(APP_PLUGIN) || target.plugins.hasPlugin(LIB_PLUGIN)) { + + // apply the translation checker plugin + target.apply { plugin(TranslationCheckerPlugin::class.java) } + } + + // apply the Google Java Format plugin + target.apply { plugin(GoogleJavaFormatPlugin::class.java) } + } + } +} diff --git a/buildSrc/src/main/java/com/itsaky/androidide/plugins/GoogleJavaFormatPlugin.kt b/buildSrc/src/main/java/com/itsaky/androidide/plugins/GoogleJavaFormatPlugin.kt new file mode 100644 index 000000000..10f472a1a --- /dev/null +++ b/buildSrc/src/main/java/com/itsaky/androidide/plugins/GoogleJavaFormatPlugin.kt @@ -0,0 +1,153 @@ +/* + * 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 + +import com.google.googlejavaformat.java.Formatter +import com.google.googlejavaformat.java.JavaFormatterOptions +import java.io.ByteArrayOutputStream +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.util.Collections +import java.util.stream.Collectors +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.logging.Logger + +/** + * Plugin for formatting the Java source files using google-java-format. + * + * @author Akash Yadav + */ +class GoogleJavaFormatPlugin : Plugin { + + override fun apply(target: Project) { + target.tasks.register("formatJavaSources") { + doLast { + val jarName = "google-java-format-1.15.0-all-deps" + val jar = target.rootProject.file(".tools/${jarName}.jar") + + if (!jar.exists()) { + logger.info("google-java-format JAR file not found. Skipping format task...") + return@doLast + } + + var formattingChanges = false + target.rootProject.subprojects.forEach { sub -> + val sources = walkForSources(sub.file("src/main/java").toPath(), logger).toMutableSet() + sources.addAll(walkForSources(sub.file("src/test/java").toPath(), logger)) + + val formatter = + Formatter( + JavaFormatterOptions.builder() + .style(JavaFormatterOptions.Style.GOOGLE) + .formatJavadoc(true) + .build() + ) + + for (path in sources) { + if (doJavaFormat(formatter, path, logger)) { + formattingChanges = true + } + } + + if (!formattingChanges) { + logger.info("No formatting changes.") + return@doLast + } + + // ---------------- Stage Changes ------------------- + var out = ByteArrayOutputStream() + var exit = + target.rootProject.exec { + isIgnoreExitValue = true + standardOutput = out + errorOutput = standardOutput + commandLine = listOf("git", "add", ".") + } + + if (exit.exitValue != 0) { + logger.error( + "Unable to stage changes. Process terminated with exit code ${exit.exitValue}" + ) + logger.error(out.toString()) + return@doLast + } + + // ----------------- Commit Changes -------------------- + out = ByteArrayOutputStream() + exit = + target.rootProject.exec { + isIgnoreExitValue = true + standardOutput = out + errorOutput = standardOutput + commandLine = listOf("git", "commit", "-m", "[Gradle] Format java source code") + } + + if (exit.exitValue == 0) { + logger.info("Changes committed successfully.") + } else { + logger.error( + "Failed to commit changes. Process terminated with exit code ${exit.exitValue}" + ) + logger.error(out.toString()) + } + } + } + } + } + + private fun doJavaFormat(formatter: Formatter, path: String, logger: Logger): Boolean { + try { + val file = Paths.get(path) + val content = Files.readAllBytes(file) + val utf8Decoded = String(content, StandardCharsets.UTF_8) + val formatted = formatter.formatSource(utf8Decoded) + if (utf8Decoded == formatted) { + logger.info("{}: UP-TO-DATE", file) + return true + } + + val utf8Encoded = formatted.toByteArray(StandardCharsets.UTF_8) + Files.write(file, utf8Encoded) + println("${path}: Formatted successfully!") + return true + } catch (th: Throwable) { + logger.error("Failed to format $path", th) + return false + } + } + + private fun walkForSources(dir: Path, logger: Logger): Collection { + try { + if (!Files.exists(dir)) { + return Collections.emptySet() + } + + return Files.walk(dir) + .filter { !Files.isDirectory(it) && Files.isReadable(it) && Files.isWritable(it) } + .map { it.toFile().absolutePath } + .filter { it.endsWith(".java") && !it.endsWith("_template.java") } + .collect(Collectors.toSet()) + } catch (error: Throwable) { + logger.error("Could not walk directory for java sources: ${dir.toString ()}", error) + return Collections.emptySet() + } + } +} diff --git a/buildSrc/src/main/java/com/itsaky/androidide/plugins/TranslationCheckerPlugin.kt b/buildSrc/src/main/java/com/itsaky/androidide/plugins/TranslationCheckerPlugin.kt new file mode 100644 index 000000000..77b7df72e --- /dev/null +++ b/buildSrc/src/main/java/com/itsaky/androidide/plugins/TranslationCheckerPlugin.kt @@ -0,0 +1,110 @@ +/* + * 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 + +import java.io.File +import java.io.FileOutputStream +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.process.ExecResult + +/** + * Plugin for generating a translation report for a module. + * + * @author Akash Yadav + */ +class TranslationCheckerPlugin : Plugin { + + override fun apply(target: Project) { + target.afterEvaluate { + tasks.register("checkTranslations") { doLast { checkTranslations(target) } } + } + } + + private fun checkTranslations(project: Project) { + val php = File("/usr/bin/php") + if (!php.exists()) { + return + } + + val resDir = project.file("src/main/res") + val strings = File(resDir, "values/strings.xml") + val reportDir = File(project.rootProject.buildDir, "translation-reports") + reportDir.delete() + + if (!resDir.exists() || !strings.exists()) { + project.logger.info("Default strings.xml file does not exist for project '${project.name}'") + return + } + + val translationDirs = + resDir.listFiles { file -> file.isDirectory && file.name.startsWith("values-") } + ?: emptyArray() + + for (dir in translationDirs) { + val translation = File(dir, "strings.xml") + if (!translation.exists()) { + project.logger.info("No translation file specifed for '${dir.name}'. Skipping..") + return + } + + val out = File(reportDir, "${project.path.replace (':', '/')}/${dir.name}.txt") + if (!out.parentFile.exists()) { + out.parentFile.mkdirs() + } + + if (out.exists()) { + out.delete() + } + + out.createNewFile() + + val result = doCheckTranslations(project, out, php, strings, translation) + + if (result.exitValue == 0) { + out.delete() + } else { + project.logger.lifecycle( + "Translation report for '${project.path}/${dir.name}' is written to '${out.absolutePath}'" + ) + } + } + } + + private fun doCheckTranslations( + project: Project, + out: File, + php: File, + strings: File, + translation: File + ): ExecResult { + val result = + project.rootProject.exec { + isIgnoreExitValue = true + standardOutput = FileOutputStream(out) + commandLine = + listOf( + php.absolutePath, + "${project.rootProject.file(".tools/strings-check.php")}", + strings.absolutePath, + translation.absolutePath + ) + } + return result + } +}