summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/build.gradle.kts2
-rw-r--r--build-logic/src/main/kotlin/app/passwordstore/gradle/crowdin/CrowdinExtension.kt6
-rw-r--r--build-logic/src/main/kotlin/app/passwordstore/gradle/crowdin/CrowdinPlugin.kt154
3 files changed, 51 insertions, 111 deletions
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9a86d2e3..5d005676 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -16,7 +16,7 @@ plugins {
}
crowdin {
- projectName = "android-password-store"
+ crowdinIdentifier = "android-password-store"
skipCleanup = false
}
diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/crowdin/CrowdinExtension.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/crowdin/CrowdinExtension.kt
index 3d45aebc..2f5cab46 100644
--- a/build-logic/src/main/kotlin/app/passwordstore/gradle/crowdin/CrowdinExtension.kt
+++ b/build-logic/src/main/kotlin/app/passwordstore/gradle/crowdin/CrowdinExtension.kt
@@ -5,16 +5,18 @@
package app.passwordstore.gradle.crowdin
+import org.gradle.api.provider.Property
+
/** Extension for configuring [CrowdinDownloadPlugin] */
interface CrowdinExtension {
/** Configure the project name on Crowdin */
- var projectName: String
+ val crowdinIdentifier: Property<String>
/**
* Don't delete downloaded and extracted translation archives from build directory.
*
* Useful for debugging.
*/
- var skipCleanup: Boolean
+ val skipCleanup: Property<Boolean>
}
diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/crowdin/CrowdinPlugin.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/crowdin/CrowdinPlugin.kt
index 98882af5..5442e085 100644
--- a/build-logic/src/main/kotlin/app/passwordstore/gradle/crowdin/CrowdinPlugin.kt
+++ b/build-logic/src/main/kotlin/app/passwordstore/gradle/crowdin/CrowdinPlugin.kt
@@ -6,18 +6,12 @@
package app.passwordstore.gradle.crowdin
import de.undercouch.gradle.tasks.download.Download
-import java.io.File
-import java.util.concurrent.TimeUnit
-import javax.xml.parsers.DocumentBuilderFactory
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.Delete
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.register
-import org.w3c.dom.Document
private const val EXCEPTION_MESSAGE =
"""Applying `crowdin-plugin` requires a projectName to be configured via the "crowdin" extension."""
@@ -29,112 +23,56 @@ class CrowdinDownloadPlugin : Plugin<Project> {
override fun apply(project: Project) {
with(project) {
- val buildDirectory = layout.buildDirectory.asFile.get()
val extension = extensions.create<CrowdinExtension>("crowdin")
- afterEvaluate {
- val projectName = extension.projectName
- if (projectName.isEmpty()) {
- throw GradleException(EXCEPTION_MESSAGE)
- }
- val buildOnApi =
- tasks.register("buildOnApi") {
- doLast {
- val login = providers.environmentVariable("CROWDIN_LOGIN")
- val key = providers.environmentVariable("CROWDIN_PROJECT_KEY")
- if (!login.isPresent) {
- throw GradleException("CROWDIN_LOGIN environment variable must be set")
- }
- if (!key.isPresent) {
- throw GradleException("CROWDIN_PROJECT_KEY environment variable must be set")
- }
- val client =
- OkHttpClient.Builder()
- .connectTimeout(5, TimeUnit.MINUTES)
- .writeTimeout(5, TimeUnit.MINUTES)
- .readTimeout(5, TimeUnit.MINUTES)
- .callTimeout(10, TimeUnit.MINUTES)
- .build()
- val url = CROWDIN_BUILD_API_URL.format(projectName, login.get(), key.get())
- val request = Request.Builder().url(url).get().build()
- client.newCall(request).execute().close()
- }
- }
- val downloadCrowdin =
- tasks.register<Download>("downloadCrowdin") {
- dependsOn(buildOnApi)
- src("https://crowdin.com/backend/download/project/$projectName.zip")
- dest("$buildDirectory/translations.zip")
- overwrite(true)
- }
- val extractCrowdin =
- tasks.register<Copy>("extractCrowdin") {
- dependsOn(downloadCrowdin)
- doFirst { File(buildDir, "translations").deleteRecursively() }
- from(zipTree("$buildDirectory/translations.zip"))
- into("$buildDirectory/translations")
- }
- val extractStrings =
- tasks.register<Copy>("extractStrings") {
- dependsOn(extractCrowdin)
- from("$buildDirectory/translations/")
- into("${projectDir}/src/")
- setFinalizedBy(setOf("removeIncompleteStrings"))
- }
- tasks.register("removeIncompleteStrings") {
- doLast {
- val sourceSets = arrayOf("main", "nonFree")
- for (sourceSet in sourceSets) {
- val fileTreeWalk = projectDir.resolve("src/$sourceSet").walkTopDown()
- val valuesDirectories =
- fileTreeWalk.filter { it.isDirectory }.filter { it.name.startsWith("values") }
- val stringFiles = fileTreeWalk.filter { it.name == "strings.xml" }
- val sourceFile =
- stringFiles.firstOrNull { it.path.endsWith("values/strings.xml") }
- ?: throw GradleException("No root strings.xml found in '$sourceSet' sourceSet")
- val sourceDoc = parseDocument(sourceFile)
- val baselineStringCount = countStrings(sourceDoc)
- val threshold = 0.80 * baselineStringCount
- stringFiles.forEach { file ->
- if (file != sourceFile) {
- val doc = parseDocument(file)
- val stringCount = countStrings(doc)
- if (stringCount < threshold) {
- file.delete()
- }
- }
- }
- valuesDirectories.forEach { dir ->
- if (dir.listFiles().isNullOrEmpty()) {
- dir.delete()
- }
- }
- }
+ val login = providers.environmentVariable("CROWDIN_LOGIN")
+ val key = providers.environmentVariable("CROWDIN_PROJECT_KEY")
+ val buildOnApi =
+ if (login.isPresent && key.isPresent) {
+ tasks.register<BuildOnApiTask>("buildOnApi") {
+ crowdinIdentifier.set(extension.crowdinIdentifier)
+ crowdinLogin.set(login)
+ crowdinKey.set(key)
}
+ } else {
+ null
}
- tasks.register("crowdin") {
- dependsOn(extractStrings)
- if (!extension.skipCleanup) {
- doLast {
- File("$buildDirectory/translations").deleteRecursively()
- File("$buildDirectory/nonFree-translations").deleteRecursively()
- File("$buildDirectory/translations.zip").delete()
- }
- }
+ val downloadCrowdin =
+ tasks.register<Download>("downloadCrowdin") {
+ if (buildOnApi != null) dependsOn(buildOnApi)
+ src(
+ "https://crowdin.com/backend/download/project/${extension.crowdinIdentifier.get()}.zip"
+ )
+ dest(layout.buildDirectory.file("translations.zip"))
+ overwrite(true)
+ }
+ val extractCrowdin =
+ tasks.register<Copy>("extractCrowdin") {
+ from(zipTree(downloadCrowdin.map { it.outputFiles.first() }))
+ into(layout.buildDirectory.dir("translations"))
}
+ val extractStrings =
+ tasks.register<Copy>("extractStrings") {
+ from(extractCrowdin.map { it.destinationDir })
+ into(layout.projectDirectory.dir("src"))
+ }
+ val removeIncompleteStrings =
+ tasks.register<StringCleanupTask>("removeIncompleteStrings") {
+ sourceDirectory.set(
+ objects.directoryProperty().fileProvider(extractStrings.map { it.destinationDir })
+ )
+ }
+ tasks.register<Delete>("crowdin") {
+ dependsOn(removeIncompleteStrings)
+ delete =
+ if (extension.skipCleanup.getOrElse(false)) {
+ emptySet()
+ } else {
+ setOf(
+ extractStrings.map { it.source },
+ downloadCrowdin.map { it.outputFiles },
+ )
+ }
}
}
}
-
- private fun parseDocument(file: File): Document {
- val dbFactory = DocumentBuilderFactory.newInstance()
- val documentBuilder = dbFactory.newDocumentBuilder()
- return documentBuilder.parse(file)
- }
-
- private fun countStrings(document: Document): Int {
- // Normalization is beneficial for us
- // https://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-how-does-it-work
- document.documentElement.normalize()
- return document.getElementsByTagName("string").length
- }
}