Moved IDE tasks in custom plugins

This commit is contained in:
Akash Yadav
2022-09-07 08:11:16 +05:30
parent 2d4d6365c1
commit 8f8d599a49
6 changed files with 325 additions and 181 deletions
+2 -170
View File
@@ -15,10 +15,10 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
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<String> 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 ())
}
}
}
+7 -5
View File
@@ -20,11 +20,13 @@ plugins {
}
repositories {
google()
gradlePluginPortal()
mavenCentral()
google()
gradlePluginPortal()
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
}
implementation(gradleApi())
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.google.googlejavaformat:google-java-format:1.15.0")
}
@@ -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"
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Project> {
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) }
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Project> {
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<String> {
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()
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Project> {
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
}
}