diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a48c982..3ba75fa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,10 @@ [versions] -kotlin = "1.8.21" -androidGradlePlugin = "8.0.1" +kotlin = "2.2.21" +androidGradlePlugin = "8.13.1" gradlePublish = "1.2.0" -detekt = "1.22.0" +detekt = "1.23.8" kotlinxSerializationJson = "1.5.1" +jvmTarget = "17" [libraries] # Android Gradle Api Plugin @@ -17,7 +18,7 @@ detekt-formating = { group = "io.gitlab.arturbosch.detekt", name = "detekt-forma detekt-rules = { group = "io.gitlab.arturbosch.detekt", name = "detekt-rules", version.ref = "detekt" } # Sample App -androidx-activityKtx = { module = "androidx.activity:activity-ktx", version = "1.7.2" } +androidx-activityKtx = { module = "androidx.activity:activity-ktx", version = "1.12.0" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } @@ -28,4 +29,4 @@ gradle-publish = { id = "com.gradle.plugin-publish", version.ref = "gradlePublis detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } [bundles] -detekt = ["detekt.formating", "detekt.rules"] +detekt = ["detekt-formating", "detekt-rules"] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fae0804..e69d040 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sampleapp/build.gradle.kts b/sampleapp/build.gradle.kts index db7208b..3f31a20 100644 --- a/sampleapp/build.gradle.kts +++ b/sampleapp/build.gradle.kts @@ -1,5 +1,3 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -8,12 +6,12 @@ plugins { android { namespace = "com.commencis.secretsvaultplugin.sampleapp" - compileSdk = 33 + compileSdk = 36 defaultConfig { applicationId = "com.commencis.secretsvaultplugin.sampleapp" - minSdk = 21 - targetSdk = 33 + minSdk = 23 + targetSdk = 36 versionCode = 1 versionName = "1.0" @@ -50,11 +48,6 @@ android { } } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - externalNativeBuild { cmake { path("src/main/cpp/CMakeLists.txt") @@ -80,9 +73,11 @@ android { } } } +} - kotlinOptions { - jvmTarget = JvmTarget.JVM_11.target +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(libs.versions.jvmTarget.get())) } } diff --git a/sampleapp/src/main/kotlin/com/commencis/secretsvaultplugin/sampleapp/MainActivity.kt b/sampleapp/src/main/kotlin/com/commencis/secretsvaultplugin/sampleapp/MainActivity.kt index 296abda..0ab8099 100644 --- a/sampleapp/src/main/kotlin/com/commencis/secretsvaultplugin/sampleapp/MainActivity.kt +++ b/sampleapp/src/main/kotlin/com/commencis/secretsvaultplugin/sampleapp/MainActivity.kt @@ -6,6 +6,9 @@ import android.view.ViewGroup import android.widget.ScrollView import android.widget.TextView import androidx.activity.ComponentActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updateLayoutParams internal class MainActivity : ComponentActivity() { @@ -51,6 +54,18 @@ internal class MainActivity : ComponentActivity() { ViewGroup.LayoutParams.MATCH_PARENT, ) ) + + ViewCompat.setOnApplyWindowInsetsListener(scrollView) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.updateLayoutParams { + topMargin = insets.top + leftMargin = insets.left + bottomMargin = insets.bottom + rightMargin = insets.right + } + WindowInsetsCompat.CONSUMED + } + } } diff --git a/secretsvaultplugin/build.gradle.kts b/secretsvaultplugin/build.gradle.kts index 3488f14..2ddca2d 100644 --- a/secretsvaultplugin/build.gradle.kts +++ b/secretsvaultplugin/build.gradle.kts @@ -1,6 +1,4 @@ import io.gitlab.arturbosch.detekt.extensions.DetektExtension -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { alias(libs.plugins.gradle.publish) @@ -17,13 +15,8 @@ dependencies { } java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 -} - -tasks.withType().configureEach { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) + toolchain { + languageVersion.set(JavaLanguageVersion.of(libs.versions.jvmTarget.get())) } } diff --git a/secretsvaultplugin/src/main/kotlin/com/commencis/secretsvaultplugin/KeepSecretsTask.kt b/secretsvaultplugin/src/main/kotlin/com/commencis/secretsvaultplugin/KeepSecretsTask.kt index 2756117..df92c5c 100644 --- a/secretsvaultplugin/src/main/kotlin/com/commencis/secretsvaultplugin/KeepSecretsTask.kt +++ b/secretsvaultplugin/src/main/kotlin/com/commencis/secretsvaultplugin/KeepSecretsTask.kt @@ -1,7 +1,5 @@ package com.commencis.secretsvaultplugin -import com.android.build.api.dsl.CommonExtension -import com.commencis.secretsvaultplugin.extensions.SecretsVaultExtension import com.commencis.secretsvaultplugin.utils.CHECK_APP_SIGNATURE_PLACEHOLDER import com.commencis.secretsvaultplugin.utils.CodeGenerator import com.commencis.secretsvaultplugin.utils.EMPTY_STRING @@ -9,14 +7,22 @@ import com.commencis.secretsvaultplugin.utils.Utils import com.commencis.secretsvaultplugin.utils.capitalize import kotlinx.serialization.json.Json import org.gradle.api.DefaultTask +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.ProjectLayout +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputDirectory -import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Optional import org.gradle.api.tasks.TaskAction import java.io.File import java.io.IOException import java.nio.charset.Charset import java.util.Locale +import javax.inject.Inject internal const val MAIN_SOURCE_SET_NAME = "main" @@ -52,62 +58,83 @@ private const val ANSI_COLOR_YELLOW = "\u001B[33m" * replaces them with obfuscated keys from the provided json file. */ @Suppress("TooManyFunctions", "UnnecessaryAbstractClass") -internal abstract class KeepSecretsTask : DefaultTask() { +internal abstract class KeepSecretsTask @Inject constructor(private val projectLayout: ProjectLayout) : DefaultTask() { /** - * Map containing the secrets + * Plugin source directory that has the cpp and kotlin files */ - private var secretsMap: Map>? = null + @get:InputDirectory + abstract val pluginSourceFolder: DirectoryProperty /** - * Map containing the source sets and their respective secrets file names + * The secrets file to read from */ - private var sourceSetToSecretFileMap: Map>? = null + @get:InputFile + abstract val secretsFile: RegularFileProperty /** - * Plugin source folder that has the cpp and kotlin files + * Optional mapping file for source sets to secret file names */ - @get:InputDirectory - abstract val pluginSourceFolder: Property + @get:InputFile + @get:Optional + abstract val sourceSetSecretsMappingFile: RegularFileProperty /** - * Represents the JSON property of the class it's abstracted in. + * The obfuscation key */ - @get:Internal - abstract val json: Property + @get:Input + abstract val obfuscationKey: Property /** - * Provides lazy access to the [SecretsVaultExtension] of the project. + * List of application signatures */ - private val secretsVaultExtension by lazy { - project.extensions.getByType(SecretsVaultExtension::class.java) - } + @get:Input + abstract val appSignatures: ListProperty /** - * Provides lazy access to the [CMakeExtension] of the project. + * The package name */ - private val cMakeExtension by lazy { - secretsVaultExtension.cmake.get() - } + @get:Input + abstract val packageName: Property /** - * Get the package name of the module on which this plugin is used - * - * The function will first attempt to get the package name from the [SecretsVaultExtension]. - * If it's not provided (i.e., it's an empty string), the function will attempt to get the namespace - * from the [CommonExtension] of the project. + * Whether to make the generated class injectable */ - private val packageName: String by lazy { - secretsVaultExtension.packageName.getOrElse(EMPTY_STRING).ifEmpty { - project.extensions.getByType(CommonExtension::class.java).namespace.orEmpty() - } - } + @get:Input + abstract val makeInjectable: Property + + /** + * CMake project name + */ + @get:Input + abstract val cmakeProjectName: Property + + /** + * CMake version + */ + @get:Input + abstract val cmakeVersion: Property + + private val projectDirectory: Directory + get() = projectLayout.projectDirectory /** * Lazily initialized instance of [CodeGenerator]. */ private val codeGenerator by lazy { CodeGenerator() } + private val json = Json { encodeDefaults = true } + + /** + * Map containing the secrets + */ + private var secretsMap: Map>? = null + + /** + * Map containing the source sets and their respective secrets file names + */ + private var sourceSetToSecretFileMap: Map>? = null + /** * The main task action which is responsible for keeping secrets */ @@ -135,7 +162,7 @@ internal abstract class KeepSecretsTask : DefaultTask() { */ @Throws(IllegalArgumentException::class) private fun getSecretsFile(): File { - val secretsFile = secretsVaultExtension.secretsFile.get() + val secretsFile = secretsFile.get().asFile require(secretsFile.exists() && secretsFile.isFile) { "${secretsFile.name} does not exist or is not a valid file!" } @@ -150,7 +177,7 @@ internal abstract class KeepSecretsTask : DefaultTask() { */ @Throws(IllegalArgumentException::class) private fun getSourceSetToSecretMappingFile(): File? { - val mappingFile = secretsVaultExtension.sourceSetSecretsMappingFile.orNull + val mappingFile = sourceSetSecretsMappingFile.orNull?.asFile if (mappingFile != null) { require(mappingFile.exists() && mappingFile.isFile) { "${mappingFile.name} does not exist or is not a valid file!" @@ -167,7 +194,7 @@ internal abstract class KeepSecretsTask : DefaultTask() { private fun initSecretsFromFile(secretsFile: File) { val content = secretsFile.readText(Charsets.UTF_8) runCatching { - secretsMap = json.get().decodeFromString(content).secrets.groupBy { it.sourceSet } + secretsMap = json.decodeFromString(content).secrets.groupBy { it.sourceSet } }.onFailure { throwable -> logger.error( """ @@ -193,7 +220,7 @@ internal abstract class KeepSecretsTask : DefaultTask() { private fun initSourceSetToSecretFileMap(mappingFile: File) { val content = mappingFile.readText(Charsets.UTF_8) runCatching { - sourceSetToSecretFileMap = json.get().decodeFromString(content).toMap() + sourceSetToSecretFileMap = json.decodeFromString(content).toMap() if (sourceSetToSecretFileMap?.any { it.value.first.name == MAIN_SOURCE_SET_SECRETS_FILE_NAME } == true) { logger.error( """ @@ -236,7 +263,9 @@ internal abstract class KeepSecretsTask : DefaultTask() { fileName: String, pathSuffix: String = EMPTY_STRING, ): File { - return project.file(SOURCE_SET_TEMPLATE.format(sourceSet.sourceSet, "cpp") + pathSuffix + fileName) + return projectDirectory + .file(SOURCE_SET_TEMPLATE.format(sourceSet.sourceSet, "cpp") + pathSuffix + fileName) + .asFile } /** @@ -249,11 +278,11 @@ internal abstract class KeepSecretsTask : DefaultTask() { private fun getKotlinDestination(sourceSet: SecretsSourceSet, fileName: String): File { val kotlinPath = SOURCE_SET_TEMPLATE.format(sourceSet.sourceSet, "kotlin") val javaPath = SOURCE_SET_TEMPLATE.format(sourceSet.sourceSet, "java") - val basePath = kotlinPath.takeIf { project.file(kotlinPath).exists() } ?: javaPath - val packagePath = packageName.replace(".", File.separator) + val basePath = kotlinPath.takeIf { projectDirectory.file(kotlinPath).asFile.exists() } ?: javaPath + val packagePath = packageName.get().replace(".", File.separator) val fullPath = basePath + packagePath - val directory = project.file(fullPath) + val directory = projectDirectory.file(fullPath).asFile if (directory.exists().not()) { logger.lifecycle("Directory $fullPath does not exist in the project, creating it.") directory.mkdirs() @@ -267,18 +296,18 @@ internal abstract class KeepSecretsTask : DefaultTask() { */ private fun copyCommonCppFiles() { runCatching { - val appSignaturesCodeBlock = secretsVaultExtension.appSignatures.get().map { appSignature -> + val appSignaturesCodeBlock = appSignatures.get().map { appSignature -> Utils.encodeSecret( secretKey = appSignature.replace(":", EMPTY_STRING), - obfuscationKey = secretsVaultExtension.obfuscationKey.get(), + obfuscationKey = obfuscationKey.get(), ) }.let { encodedAppSignatures -> codeGenerator.getAppSignatureCheck(encodedAppSignatures) } - project.file("${pluginSourceFolder.get().path}/cpp/common/").listFiles()?.forEach { file -> + pluginSourceFolder.dir("cpp/common/").get().asFile.listFiles()?.forEach { file -> var text = file.readText(Charset.defaultCharset()) if (file.name == SECRETS_UTIL_CPP_FILE_NAME) { - text = text.replace(OBFUSCATION_KEY_PLACEHOLDER, secretsVaultExtension.obfuscationKey.get()) + text = text.replace(OBFUSCATION_KEY_PLACEHOLDER, obfuscationKey.get()) .replace(CHECK_APP_SIGNATURE_PLACEHOLDER, appSignaturesCodeBlock) } val destination = getCppDestination( @@ -302,7 +331,7 @@ internal abstract class KeepSecretsTask : DefaultTask() { */ private fun copySecretCppFile(sourceSet: SecretsSourceSet) { runCatching { - val secretsFile = project.file("${pluginSourceFolder.get().path}/cpp/$SECRETS_CPP_FILE_NAME") + val secretsFile = pluginSourceFolder.dir("cpp/$SECRETS_CPP_FILE_NAME").get().asFile val text = secretsFile.readText(Charset.defaultCharset()).replace( oldValue = COMMON_FOLDER_PATH_PREFIX_PLACEHOLDER, newValue = if (sourceSet == SecretsSourceSet(MAIN_SOURCE_SET_NAME)) { @@ -328,11 +357,11 @@ internal abstract class KeepSecretsTask : DefaultTask() { private fun copyCMakeListsFile(sourceSets: Set) { runCatching { val mainSourceSet = SecretsSourceSet(MAIN_SOURCE_SET_NAME) - val file = project.file("${pluginSourceFolder.get().path}/cpp/$C_MAKE_LISTS_FILE_NAME") + val file = pluginSourceFolder.dir("cpp/$C_MAKE_LISTS_FILE_NAME").get().asFile val textBuilder = StringBuilder( file.readText(Charset.defaultCharset()) - .replace(PROJECT_NAME_PLACEHOLDER, cMakeExtension.projectName.get()) - .replace(CMAKE_VERSION_PLACEHOLDER, cMakeExtension.version.get()) + .replace(PROJECT_NAME_PLACEHOLDER, cmakeProjectName.get()) + .replace(CMAKE_VERSION_PLACEHOLDER, cmakeVersion.get()) ) if (sourceSets.contains(mainSourceSet)) { val fileName = getKotlinSecretsFileName(mainSourceSet).removeSuffix(KOTLIN_FILE_NAME_SUFFIX) @@ -378,18 +407,18 @@ internal abstract class KeepSecretsTask : DefaultTask() { private fun copyKotlinFile(sourceSet: SecretsSourceSet) { runCatching { - val kotlinFileName = if (secretsVaultExtension.makeInjectable.get()) { + val kotlinFileName = if (makeInjectable.get()) { TEMP_KOTLIN_INJECTABLE_FILE_NAME } else { TEMP_KOTLIN_NOT_INJECTABLE_FILE_NAME } - val kotlinFiles = project.file("${pluginSourceFolder.get().path}/kotlin/").listFiles() + val kotlinFiles = pluginSourceFolder.dir("kotlin").get().asFile.listFiles() val kotlinFile = kotlinFiles?.find { file -> file.name == kotlinFileName } ?: throw IOException("Kotlin file that will be copied not found") var text = kotlinFile.readText(Charset.defaultCharset()) val fileName = getKotlinSecretsFileName(sourceSet) - text = text.replace(PACKAGE_PLACEHOLDER, packageName) + text = text.replace(PACKAGE_PLACEHOLDER, packageName.get()) .replace(SECRETS_CLASS_NAME_PLACEHOLDER, fileName.removeSuffix(KOTLIN_FILE_NAME_SUFFIX)) val destination = getKotlinDestination( sourceSet = sourceSet, @@ -480,13 +509,13 @@ internal abstract class KeepSecretsTask : DefaultTask() { var kotlinPackage = Utils.getKotlinFilePackage(secretsKotlin) if (kotlinPackage.isNullOrEmpty()) { logWarning("Empty package in $fileName") - kotlinPackage = packageName + kotlinPackage = packageName.get() } val secretsCpp = getCppDestination(sourceSet = sourceSet, fileName = SECRETS_CPP_FILE_NAME) secrets.forEach { secret -> val (key, value) = secret - val obfuscatedValue = Utils.encodeSecret(value, secretsVaultExtension.obfuscationKey.get()) + val obfuscatedValue = Utils.encodeSecret(value, obfuscationKey.get()) val cppText = secretsCpp.readText(Charset.defaultCharset()) val keyName = "$JVM_NAME_PREFIX${secretKeyToIndexMap[key]}" if (cppText.contains(keyName)) { diff --git a/secretsvaultplugin/src/main/kotlin/com/commencis/secretsvaultplugin/SecretsVaultPlugin.kt b/secretsvaultplugin/src/main/kotlin/com/commencis/secretsvaultplugin/SecretsVaultPlugin.kt index 2cc666a..c0c8510 100644 --- a/secretsvaultplugin/src/main/kotlin/com/commencis/secretsvaultplugin/SecretsVaultPlugin.kt +++ b/secretsvaultplugin/src/main/kotlin/com/commencis/secretsvaultplugin/SecretsVaultPlugin.kt @@ -1,9 +1,9 @@ package com.commencis.secretsvaultplugin +import com.android.build.api.dsl.CommonExtension import com.commencis.secretsvaultplugin.extensions.CMakeExtension import com.commencis.secretsvaultplugin.extensions.SecretsVaultExtension import com.commencis.secretsvaultplugin.utils.Utils -import kotlinx.serialization.json.Json import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.tasks.Copy @@ -57,14 +57,37 @@ internal class SecretsVaultPlugin : Plugin { into(project.layout.buildDirectory.dir("intermediates/secrets_vault_plugin")) } + val secretsVaultExtension = project.extensions.getByType(SecretsVaultExtension::class.java) + /** * Create a gradle task to keep secrets from a json file. */ project.tasks.register(TASK_KEEP_SECRETS_FROM_JSON_FILE, KeepSecretsTask::class.java).configure { group = TASK_GROUP description = "Re-generate and obfuscate keys from the json file and add it to your Android project" - pluginSourceFolder.set(unzipTaskProvider.map { task -> task.destinationDir }) - json.set(Json { encodeDefaults = true }) + pluginSourceFolder.set( + project.layout.projectDirectory.dir( + unzipTaskProvider.map { task -> task.destinationDir.absolutePath } + ) + ) + secretsFile.set(secretsVaultExtension.secretsFile) + sourceSetSecretsMappingFile.set(secretsVaultExtension.sourceSetSecretsMappingFile) + obfuscationKey.set(secretsVaultExtension.obfuscationKey) + appSignatures.set(secretsVaultExtension.appSignatures) + makeInjectable.set(secretsVaultExtension.makeInjectable) + + // Provide the package name in the extension unless it is not specified, + // in which case the namespace is used. + packageName.set( + secretsVaultExtension.packageName.orElse( + project.provider { + project.extensions.findByType(CommonExtension::class.java)?.namespace.orEmpty() + } + ) + ) + + cmakeProjectName.set(secretsVaultExtension.cmake.flatMap { cMakeExtension -> cMakeExtension.projectName }) + cmakeVersion.set(secretsVaultExtension.cmake.flatMap { cMakeExtension -> cMakeExtension.version }) } } @@ -91,7 +114,7 @@ internal class SecretsVaultPlugin : Plugin { private fun createSecretsVaultExtension(project: Project, cmakeExtension: CMakeExtension): SecretsVaultExtension { return project.extensions.create(EXTENSION_NAME_SECRETS_VAULT, SecretsVaultExtension::class.java).apply { obfuscationKey.convention(Utils.generateObfuscationKey()) - secretsFile.convention(project.file(DEFAULT_SECRETS_FILE_NAME)) + secretsFile.convention(project.layout.projectDirectory.file(DEFAULT_SECRETS_FILE_NAME)) appSignatures.convention(emptyList()) makeInjectable.convention(DEFAULT_MAKE_INJECTABLE_VALUE) cmake.convention(cmakeExtension) diff --git a/secretsvaultplugin/src/main/kotlin/com/commencis/secretsvaultplugin/extensions/SecretsVaultExtension.kt b/secretsvaultplugin/src/main/kotlin/com/commencis/secretsvaultplugin/extensions/SecretsVaultExtension.kt index 58f91b6..26360fd 100644 --- a/secretsvaultplugin/src/main/kotlin/com/commencis/secretsvaultplugin/extensions/SecretsVaultExtension.kt +++ b/secretsvaultplugin/src/main/kotlin/com/commencis/secretsvaultplugin/extensions/SecretsVaultExtension.kt @@ -1,9 +1,9 @@ package com.commencis.secretsvaultplugin.extensions import com.commencis.secretsvaultplugin.utils.EMPTY_STRING +import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property -import java.io.File /** * Provides extension properties for the SecretVault, allowing configurations to be set. @@ -25,8 +25,8 @@ import java.io.File */ interface SecretsVaultExtension { val obfuscationKey: Property - val secretsFile: Property - val sourceSetSecretsMappingFile: Property + val secretsFile: RegularFileProperty + val sourceSetSecretsMappingFile: RegularFileProperty val appSignatures: ListProperty val packageName: Property val makeInjectable: Property