diff --git a/Makefile b/Makefile index 7b78c6efcf..d40dde3fed 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ includeAnnotation?= useCompositeBuild=true dry_run=false instrumentation=Ui +stacktrace?= params?= @@ -28,6 +29,12 @@ ifdef infra params +=-PinfraVersion=$(infra) endif +params +=-PtestBuildType=$(test_build_type) +params +=-Pci=$(ci) +params +=$(log_level) +params +=-PkubernetesContext=$(kubernetesContext) +params +=-PuseCompositeBuild=$(useCompositeBuild) + ifeq ($(gradle_debug),true) params +=-Dorg.gradle.debug=true --no-daemon endif @@ -36,13 +43,9 @@ ifeq ($(dry_run),true) params +=--dry-run endif -params +=-PtestBuildType=$(test_build_type) -params +=-Pci=$(ci) -params +=$(log_level) -params +=-PkubernetesContext=$(kubernetesContext) -params +=-PuseCompositeBuild=$(useCompositeBuild) - -module=test-app +ifdef stacktrace +params +=--stacktrace +endif help: ./gradlew help $(params) diff --git a/buildscript/src/main/kotlin/Dependencies.kt b/buildscript/src/main/kotlin/Dependencies.kt index abad7cb1c0..3f8ddbff96 100644 --- a/buildscript/src/main/kotlin/Dependencies.kt +++ b/buildscript/src/main/kotlin/Dependencies.kt @@ -63,7 +63,7 @@ object Dependencies { // 1.6.x <-> AGP 3.6.x // 2.0.x <-> AGP 4.0.x // 2.1.x <-> AGP 4.1.x - const val r8 = "com.android.tools:r8:2.0.94" + const val r8 = "com.android.tools:r8:2.1.80" const val proguardRetrace = "net.sf.proguard:proguard-retrace:6.2.2" const val playServicesMaps = "com.google.android.gms:play-services-maps:17.0.0" const val appcompat = "androidx.appcompat:appcompat:${Versions.androidX}" diff --git a/samples/test-app/proguard-rules.pro b/samples/test-app/proguard-rules.pro index e9caf6359a..2f6f2c6b38 100644 --- a/samples/test-app/proguard-rules.pro +++ b/samples/test-app/proguard-rules.pro @@ -1 +1,5 @@ -dontobfuscate + +# Workaround for shrinked R class in test code: https://github.com/slackhq/keeper/issues/22 +# Consuming the same rules from ui-testing-core is not enough. We need them in a regular APK not a test one. +-keep class com.google.android.material.R$id { *; } diff --git a/settings.gradle.kts b/settings.gradle.kts index 243589aa6f..57e4ffdc83 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -129,7 +129,7 @@ pluginManagement { useModule("com.avito.android:${pluginId.removePrefix("com.avito.android.")}:$infraVersion") pluginId == "com.slack.keeper" -> - useModule("com.slack.keeper:keeper:0.6.0") + useModule("com.slack.keeper:keeper:0.7.0") } } } diff --git a/subprojects/android-test/ui-testing-core/proguard.pro b/subprojects/android-test/ui-testing-core/proguard.pro new file mode 100644 index 0000000000..6d2a1b8573 --- /dev/null +++ b/subprojects/android-test/ui-testing-core/proguard.pro @@ -0,0 +1,2 @@ +# Workaround for shrinked R class in test code: https://github.com/slackhq/keeper/issues/22 +-keep class com.google.android.material.R$id { *; } diff --git a/subprojects/gradle.properties b/subprojects/gradle.properties index 00e0491e63..796d05656a 100644 --- a/subprojects/gradle.properties +++ b/subprojects/gradle.properties @@ -10,6 +10,6 @@ android.useAndroidX=true projectVersion=2020.31 # TODO: MBS-9285 - https://github.com/gradle/gradle/issues/12660 systemProp.kotlinVersion=1.3.72 -systemProp.androidGradlePluginVersion=4.0.2 +systemProp.androidGradlePluginVersion=4.1.1 # Disable console output https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin/issues/202 systemProp.dependency.analysis.silent=true diff --git a/subprojects/gradle/android/src/main/kotlin/com/avito/android/ApplicationVariantExtensions.kt b/subprojects/gradle/android/src/main/kotlin/com/avito/android/ApplicationVariantExtensions.kt deleted file mode 100644 index 5ede01dd8b..0000000000 --- a/subprojects/gradle/android/src/main/kotlin/com/avito/android/ApplicationVariantExtensions.kt +++ /dev/null @@ -1,17 +0,0 @@ -@file:Suppress("UnstableApiUsage") - -package com.avito.android - -import com.android.build.gradle.api.ApplicationVariant -import org.gradle.api.provider.Provider -import java.io.File - -fun ApplicationVariant.apkFileProvider(): Provider = packageApplicationProvider.map { it.getApkFile() } - -fun ApplicationVariant.bundleFileProvider(): Provider = apkFileProvider().map { apkFile -> - val bundleDir = apkFile.parentFile.absolutePath.replace("apk", "bundle") - val bundleName = apkFile.name - .replace(".apk", ".aab") - .replace("-unsigned", "") - File(bundleDir, bundleName) -} diff --git a/subprojects/gradle/android/src/main/kotlin/com/avito/android/PackageAndroidArtifactExtensions.kt b/subprojects/gradle/android/src/main/kotlin/com/avito/android/PackageAndroidArtifactExtensions.kt index 738e818a02..115d94945f 100644 --- a/subprojects/gradle/android/src/main/kotlin/com/avito/android/PackageAndroidArtifactExtensions.kt +++ b/subprojects/gradle/android/src/main/kotlin/com/avito/android/PackageAndroidArtifactExtensions.kt @@ -1,14 +1,33 @@ package com.avito.android import com.android.build.gradle.tasks.PackageAndroidArtifact +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty import java.io.File -fun PackageAndroidArtifact.getApkFile(): File { - require(apkNames.size == 1) { - "Cannot get apk from $name android artifact task because apkNames.size != 1 ($apkNames). Split apk is not supported." +// TODO: Use Artifacts API +fun PackageAndroidArtifact.apkDirectory(): DirectoryProperty = outputDirectory + +fun Directory.getApk(): File? { + val dir = asFile + val apks = dir.listFiles().orEmpty() + .filter { + it.extension == "apk" + } + + require(apks.size < 2) { + "Multiple APK are not supported: ${dir.dumpFiles()}" } + return apks.firstOrNull() +} - val outputDir = outputDirectory.get().asFile +fun Directory.getApkOrThrow(): File { + return requireNotNull(getApk()) { + "APK not found in ${asFile}. Files in dir: ${asFile.dumpFiles()}" + } +} - return File(outputDir, apkNames.first()) +private fun File.dumpFiles(): String { + return listFiles().orEmpty() + .joinToString(prefix = "[", postfix = "]") { it.path } } diff --git a/subprojects/gradle/android/src/main/kotlin/com/avito/android/ProjectExtensions.kt b/subprojects/gradle/android/src/main/kotlin/com/avito/android/ProjectExtensions.kt index d6974b67d5..39b2e5820b 100644 --- a/subprojects/gradle/android/src/main/kotlin/com/avito/android/ProjectExtensions.kt +++ b/subprojects/gradle/android/src/main/kotlin/com/avito/android/ProjectExtensions.kt @@ -1,5 +1,8 @@ package com.avito.android +import com.android.build.api.component.ComponentIdentity +import com.android.build.api.dsl.AndroidSourceSet +import com.android.build.api.dsl.CommonExtension import com.android.build.gradle.AppExtension import com.android.build.gradle.AppPlugin import com.android.build.gradle.BaseExtension @@ -26,6 +29,10 @@ fun Project.withAndroidModule(block: (testedExtension: TestedExtension) -> Unit) withAndroidLib(block) } +@Suppress("UnstableApiUsage") +val Project.androidCommonExtension + get() = extensions.getByType(CommonExtension::class.java) + val Project.androidBaseExtension: BaseExtension get() = extensions.getByName("android") @@ -44,5 +51,10 @@ fun Project.isAndroidApp(): Boolean = fun Project.isAndroidLibrary(): Boolean = plugins.hasPlugin("com.android.library") +@Suppress("DefaultLocale") fun TaskContainer.bundleTaskProvider(variant: ApplicationVariant): TaskProvider<*> = named("bundle${variant.name.capitalize()}") + +@Suppress("DefaultLocale", "UnstableApiUsage") +fun taskName(prefix: String, component: ComponentIdentity) = + prefix + component.flavorName.capitalize() + component.buildType.orEmpty().capitalize() diff --git a/subprojects/gradle/cd/src/test/kotlin/com/avito/ci/steps/UploadCdBuildResultIntegrationTest.kt b/subprojects/gradle/cd/src/test/kotlin/com/avito/ci/steps/UploadCdBuildResultIntegrationTest.kt index d7988e6a92..ce9acc6dee 100644 --- a/subprojects/gradle/cd/src/test/kotlin/com/avito/ci/steps/UploadCdBuildResultIntegrationTest.kt +++ b/subprojects/gradle/cd/src/test/kotlin/com/avito/ci/steps/UploadCdBuildResultIntegrationTest.kt @@ -169,7 +169,6 @@ class RealTest { val configFile = projectDir.file(configFileName) configFile.writeText(cdBuildConfig) - projectDir.file("/app/build/outputs/apk/release/app-release.apk").createNewFile() projectDir.file("/app/build/reports/mapping.txt").writeText("1") val cdBuildResultRequest = dispatcher.captureRequest { @@ -239,7 +238,6 @@ class RealTest { val configFile = projectDir.file(configFileName) configFile.writeText(cdBuildConfig) - projectDir.file("/app/build/outputs/apk/release/app-release.apk").createNewFile() projectDir.file("/app/build/reports/mapping.txt").writeText("1") dispatcher.registerMock( diff --git a/subprojects/gradle/instrumentation-tests-test-fixtures/src/main/kotlin/com/avito/instrumentation/FakeInstrumentationTestsActionParams.kt b/subprojects/gradle/instrumentation-tests-test-fixtures/src/main/kotlin/com/avito/instrumentation/FakeInstrumentationTestsActionParams.kt index 0e92500c69..c08a99dc73 100644 --- a/subprojects/gradle/instrumentation-tests-test-fixtures/src/main/kotlin/com/avito/instrumentation/FakeInstrumentationTestsActionParams.kt +++ b/subprojects/gradle/instrumentation-tests-test-fixtures/src/main/kotlin/com/avito/instrumentation/FakeInstrumentationTestsActionParams.kt @@ -15,8 +15,6 @@ import java.io.File fun InstrumentationTestsAction.Params.Companion.createStubInstance( mainApk: File = File(""), testApk: File = File(""), - apkOnTargetCommit: File = File(""), - testApkOnTargetCommit: File = File(""), instrumentationConfiguration: InstrumentationConfiguration.Data = InstrumentationConfiguration.Data.createStubInstance(), executionParameters: ExecutionParameters = ExecutionParameters.createStubInstance(), buildId: String = "33456", @@ -46,8 +44,6 @@ fun InstrumentationTestsAction.Params.Companion.createStubInstance( InstrumentationTestsAction.Params( mainApk = mainApk, testApk = testApk, - apkOnTargetCommit = apkOnTargetCommit, - testApkOnTargetCommit = testApkOnTargetCommit, instrumentationConfiguration = instrumentationConfiguration, executionParameters = executionParameters, buildId = buildId, diff --git a/subprojects/gradle/instrumentation-tests/src/main/kotlin/com/avito/instrumentation/InstrumentationTestsAction.kt b/subprojects/gradle/instrumentation-tests/src/main/kotlin/com/avito/instrumentation/InstrumentationTestsAction.kt index de92c03113..4af8aa659b 100644 --- a/subprojects/gradle/instrumentation-tests/src/main/kotlin/com/avito/instrumentation/InstrumentationTestsAction.kt +++ b/subprojects/gradle/instrumentation-tests/src/main/kotlin/com/avito/instrumentation/InstrumentationTestsAction.kt @@ -48,8 +48,6 @@ class InstrumentationTestsAction( data class Params( val mainApk: File?, val testApk: File, - val apkOnTargetCommit: File?, - val testApkOnTargetCommit: File?, val instrumentationConfiguration: InstrumentationConfiguration.Data, val executionParameters: ExecutionParameters, val buildId: String, diff --git a/subprojects/gradle/instrumentation-tests/src/main/kotlin/com/avito/instrumentation/InstrumentationTestsPlugin.kt b/subprojects/gradle/instrumentation-tests/src/main/kotlin/com/avito/instrumentation/InstrumentationTestsPlugin.kt index 4d851027b1..43f62b6d0b 100644 --- a/subprojects/gradle/instrumentation-tests/src/main/kotlin/com/avito/instrumentation/InstrumentationTestsPlugin.kt +++ b/subprojects/gradle/instrumentation-tests/src/main/kotlin/com/avito/instrumentation/InstrumentationTestsPlugin.kt @@ -7,7 +7,7 @@ import com.android.build.gradle.api.ApplicationVariant import com.android.build.gradle.api.TestVariant import com.android.build.gradle.internal.dsl.DefaultConfig import com.android.build.gradle.internal.tasks.ProguardConfigurableTask -import com.avito.android.getApkFile +import com.avito.android.apkDirectory import com.avito.android.withAndroidApp import com.avito.android.withAndroidLib import com.avito.android.withAndroidModule @@ -144,7 +144,7 @@ class InstrumentationTestsPlugin : Plugin { if (extensionData.testApplicationApk == null) { task.dependencyOn(testApkProvider) { dependentTask -> - task.testApplication.set(dependentTask.getApkFile()) + task.testApplication.set(dependentTask.apkDirectory()) } } else { task.testApplication.set(File(extensionData.testApplicationApk)) @@ -189,7 +189,7 @@ class InstrumentationTestsPlugin : Plugin { if (extensionData.applicationApk == null) { task.dependencyOn(testedVariantPackageTask) { dependentTask -> - task.application.set(dependentTask.getApkFile()) + task.application.set(dependentTask.apkDirectory()) } } else { task.application.set(File(extensionData.applicationApk)) @@ -197,7 +197,7 @@ class InstrumentationTestsPlugin : Plugin { if (extensionData.testApplicationApk == null) { task.dependencyOn(testVariantPackageTask) { dependentTask -> - task.testApplication.set(dependentTask.getApkFile()) + task.testApplication.set(dependentTask.apkDirectory()) } } else { task.testApplication.set(File(extensionData.testApplicationApk)) diff --git a/subprojects/gradle/instrumentation-tests/src/main/kotlin/com/avito/instrumentation/InstrumentationTestsTask.kt b/subprojects/gradle/instrumentation-tests/src/main/kotlin/com/avito/instrumentation/InstrumentationTestsTask.kt index 4cc949274f..fcdaf33b70 100644 --- a/subprojects/gradle/instrumentation-tests/src/main/kotlin/com/avito/instrumentation/InstrumentationTestsTask.kt +++ b/subprojects/gradle/instrumentation-tests/src/main/kotlin/com/avito/instrumentation/InstrumentationTestsTask.kt @@ -1,6 +1,8 @@ package com.avito.instrumentation import com.avito.android.build_verdict.BuildVerdictTask +import com.avito.android.getApk +import com.avito.android.getApkOrThrow import com.avito.cd.buildOutput import com.avito.gradle.worker.inMemoryWork import com.avito.instrumentation.configuration.ImpactAnalysisPolicy @@ -16,12 +18,7 @@ import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.ObjectFactory -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.Optional -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.* import org.gradle.kotlin.dsl.property import org.gradle.workers.WorkerExecutor import javax.inject.Inject @@ -33,11 +30,11 @@ abstract class InstrumentationTestsTask @Inject constructor( ) : DefaultTask(), BuildVerdictTask { @Optional - @InputFile - val application: RegularFileProperty = objects.fileProperty() + @InputDirectory + val application: DirectoryProperty = objects.directoryProperty() - @InputFile - val testApplication: RegularFileProperty = objects.fileProperty() + @InputDirectory + val testApplication: DirectoryProperty = objects.directoryProperty() @Input val impactAnalysisPolicy = objects.property() @@ -54,14 +51,6 @@ abstract class InstrumentationTestsTask @Inject constructor( @InputFile val affectedTests: RegularFileProperty = objects.fileProperty() - @Optional - @InputFile - val apkOnTargetCommit: RegularFileProperty = objects.fileProperty() - - @Optional - @InputFile - val testApkOnTargetCommit: RegularFileProperty = objects.fileProperty() - @Optional @InputFile val applicationProguardMapping: RegularFileProperty = objects.fileProperty() @@ -144,10 +133,8 @@ abstract class InstrumentationTestsTask @Inject constructor( workerExecutor.inMemoryWork { InstrumentationTestsAction( InstrumentationTestsAction.Params( - mainApk = application.orNull?.asFile, - testApk = testApplication.get().asFile, - apkOnTargetCommit = apkOnTargetCommit.orNull?.asFile, - testApkOnTargetCommit = testApkOnTargetCommit.orNull?.asFile, + mainApk = application.orNull?.getApk(), + testApk = testApplication.get().getApkOrThrow(), instrumentationConfiguration = configuration, executionParameters = parameters.get(), buildId = buildId.get(), diff --git a/subprojects/gradle/instrumentation-tests/src/test/kotlin/com/avito/instrumentation/InstrumentationTestsActionIntegrationTest.kt b/subprojects/gradle/instrumentation-tests/src/test/kotlin/com/avito/instrumentation/InstrumentationTestsActionIntegrationTest.kt index 35db435254..9853e57620 100644 --- a/subprojects/gradle/instrumentation-tests/src/test/kotlin/com/avito/instrumentation/InstrumentationTestsActionIntegrationTest.kt +++ b/subprojects/gradle/instrumentation-tests/src/test/kotlin/com/avito/instrumentation/InstrumentationTestsActionIntegrationTest.kt @@ -82,11 +82,7 @@ internal class InstrumentationTestsActionIntegrationTest { ) reportsApi.enqueueTestsForRunId(reportCoordinates, Try.Success(emptyList())) - createAction( - configuration = configuration, - apkOnTargetCommit = apk, - testApkOnTargetCommit = apk - ).run() + createAction(configuration = configuration).run() assertThat(buildFailer.lastReason).isNull() } @@ -151,12 +147,8 @@ internal class InstrumentationTestsActionIntegrationTest { private fun createAction( configuration: InstrumentationConfiguration.Data, - apkOnTargetCommit: File = File(""), - testApkOnTargetCommit: File = File(""), params: InstrumentationTestsAction.Params = params( - configuration, - apkOnTargetCommit, - testApkOnTargetCommit + configuration ) ) = InstrumentationTestsAction( params = params, @@ -175,12 +167,8 @@ internal class InstrumentationTestsActionIntegrationTest { ) private fun params( - instrumentationConfiguration: InstrumentationConfiguration.Data, - apkOnTargetCommit: File, - testApkOnTargetCommit: File + instrumentationConfiguration: InstrumentationConfiguration.Data ) = InstrumentationTestsAction.Params.createStubInstance( - apkOnTargetCommit = apkOnTargetCommit, - testApkOnTargetCommit = testApkOnTargetCommit, instrumentationConfiguration = instrumentationConfiguration, logger = logger, outputDir = outputDir, diff --git a/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignApkTask.kt b/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignApkTask.kt new file mode 100644 index 0000000000..913bf42080 --- /dev/null +++ b/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignApkTask.kt @@ -0,0 +1,45 @@ +package com.avito.plugin + +import com.avito.android.getApkOrThrow +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.model.ObjectFactory +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.OutputDirectory +import java.io.File +import javax.inject.Inject + +@Suppress("UnstableApiUsage") +abstract class SignApkTask @Inject constructor(objects: ObjectFactory) : SignArtifactTask(objects) { + + @InputDirectory + val unsignedDirProperty: DirectoryProperty = objects.directoryProperty() + + @OutputDirectory + val signedDirProperty: DirectoryProperty = objects.directoryProperty() + + override fun unsignedFile(): File { + return unsignedDirProperty.get().getApkOrThrow() + } + + override fun signedFile(): File { + return File(signedDirProperty.get().asFile, signedFileName()) + } + + private fun signedFileName(): String { + val unsignedFile = unsignedFile() + + val name = unsignedFile.nameWithoutExtension.removeSuffix("-unsigned") + val extension = unsignedFile.extension + + return "$name.$extension" + } + + override fun hackForArtifactsApi() { + val signedFile = signedFile() + val copyOfSigned = File(unsignedDirProperty.get().asFile, signedFileName()) + if (!copyOfSigned.exists()) { + copyOfSigned.createNewFile() + } + signedFile.copyTo(copyOfSigned, overwrite = true) + } +} diff --git a/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignTask.kt b/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignArtifactTask.kt similarity index 59% rename from subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignTask.kt rename to subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignArtifactTask.kt index 551a21a4f2..5b5a966a6f 100644 --- a/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignTask.kt +++ b/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignArtifactTask.kt @@ -3,17 +3,16 @@ package com.avito.plugin import com.avito.utils.BuildFailer import com.avito.utils.logging.ciLogger import org.gradle.api.DefaultTask -import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.property +import java.io.File import javax.inject.Inject @Suppress("UnstableApiUsage") -abstract class SignTask @Inject constructor(objects: ObjectFactory) : DefaultTask() { +abstract class SignArtifactTask @Inject constructor(objects: ObjectFactory) : DefaultTask() { @Input val tokenProperty = objects.property() @@ -21,16 +20,22 @@ abstract class SignTask @Inject constructor(objects: ObjectFactory) : DefaultTas @Input val serviceUrl = objects.property() - @InputFile - val unsignedFileProperty: RegularFileProperty = objects.fileProperty() + protected abstract fun unsignedFile(): File - @OutputFile - val signedFileProperty: RegularFileProperty = objects.fileProperty() + protected abstract fun signedFile(): File + + /** + * TODO: find a better way to profile a final artifact to CI + * Results of transformations are stored in build/intermediates/bundle/release//out + * It's accessible by Artifacts API programmatically but we need a final one file. + * We rewrite an original file to preserve legacy behaviour. + */ + protected abstract fun hackForArtifactsApi() @TaskAction fun run() { - val unsignedFile = unsignedFileProperty.get().asFile - val signedFile = signedFileProperty.get().asFile + val unsignedFile = unsignedFile() + val signedFile = signedFile() val serviceUrl: String = serviceUrl.get() @@ -42,6 +47,8 @@ abstract class SignTask @Inject constructor(objects: ObjectFactory) : DefaultTas ciLogger = ciLogger ).sign() // TODO: User workers + hackForArtifactsApi() + val buildFailer = BuildFailer.RealFailer() signResult.fold( diff --git a/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignBundleTask.kt b/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignBundleTask.kt new file mode 100644 index 0000000000..734c70323d --- /dev/null +++ b/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignBundleTask.kt @@ -0,0 +1,30 @@ +package com.avito.plugin + +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.model.ObjectFactory +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputFile +import java.io.File +import javax.inject.Inject + +@Suppress("UnstableApiUsage") +abstract class SignBundleTask @Inject constructor(objects: ObjectFactory) : SignArtifactTask(objects) { + + @InputFile + val unsignedFileProperty: RegularFileProperty = objects.fileProperty() + + @OutputFile + val signedFileProperty: RegularFileProperty = objects.fileProperty() + + override fun unsignedFile(): File { + return unsignedFileProperty.get().asFile + } + + override fun signedFile(): File { + return signedFileProperty.get().asFile + } + + override fun hackForArtifactsApi() { + signedFile().copyTo(unsignedFile(), overwrite = true) + } +} diff --git a/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignServicePlugin.kt b/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignServicePlugin.kt index 973a79213f..4a18907a68 100644 --- a/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignServicePlugin.kt +++ b/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignServicePlugin.kt @@ -1,24 +1,21 @@ package com.avito.plugin import com.android.build.gradle.api.ApplicationVariant -import com.android.build.gradle.internal.tasks.factory.dependsOn -import com.avito.android.apkFileProvider -import com.avito.android.bundleFileProvider -import com.avito.android.bundleTaskProvider +import com.avito.android.androidCommonExtension import com.avito.android.withAndroidApp import com.avito.kotlin.dsl.getBooleanProperty import com.avito.kotlin.dsl.hasTasks import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.execution.TaskExecutionGraph -import org.gradle.api.provider.Provider +import com.android.build.api.variant.Variant +import com.android.build.api.artifact.ArtifactType +import com.avito.android.bundleTaskProvider import org.gradle.api.tasks.TaskContainer -import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.create -import org.gradle.kotlin.dsl.register import org.gradle.kotlin.dslx.closureOf import org.gradle.util.Path -import java.io.File +import java.util.Objects.requireNonNull import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -88,29 +85,62 @@ class SignServicePlugin : Plugin { target.withAndroidApp { appExtension -> - appExtension.applicationVariants.all { variant: ApplicationVariant -> + target.androidCommonExtension.onVariants { + val variant = this registerTask( tasks = target.tasks, - variant = variant, - taskName = signApkTaskName(variant.name), + type = SignApkTask::class.java, + variant = this, + taskName = signApkTaskName(variant), serviceUrl = signExtension.host.orEmpty(), - archiveProvider = variant.apkFileProvider(), - providingTask = variant.packageApplicationProvider, signTokensMap = signExtension.apkSignTokens ) registerTask( tasks = target.tasks, - variant = variant, - taskName = signBundleTaskName(variant.name), + type = SignBundleTask::class.java, + variant = this, + taskName = signBundleTaskName(variant), serviceUrl = signExtension.host.orEmpty(), - archiveProvider = variant.bundleFileProvider(), - providingTask = target.tasks.bundleTaskProvider(variant), signTokensMap = signExtension.bundleSignTokens ) - registeredBuildTypes[variant.name] = variant.buildType.name + registeredBuildTypes[variant.name] = requireNotNull(variant.buildType) + } + + target.androidCommonExtension.onVariantProperties { + + artifacts.use(target.tasks.signedApkTaskProvider(this)) + .wiredWithDirectories( + taskInput = SignApkTask::unsignedDirProperty, + taskOutput = SignApkTask::signedDirProperty + ) + .toTransform(ArtifactType.APK) + + artifacts.use(target.tasks.signedBundleTaskProvider(this)) + .wiredWithFiles( + taskInput = SignBundleTask::unsignedFileProperty, + taskOutput = SignBundleTask::signedFileProperty + ) + .toTransform(ArtifactType.BUNDLE) + } + + appExtension.applicationVariants.all { variant: ApplicationVariant -> + + val buildTypeName = variant.buildType.name + val apkToken: String? = signExtension.apkSignTokens[buildTypeName] + val bundleToken: String? = signExtension.bundleSignTokens[buildTypeName] + + variant.outputsAreSigned = apkToken.hasContent() || bundleToken.hasContent() + + target.tasks.signedApkTaskProvider(variant.name).configure { + it.dependsOn(variant.packageApplicationProvider) + } + + target.tasks.signedBundleTaskProvider(variant.name).configure { + it.dependsOn(target.tasks.bundleTaskProvider(variant)) + } } } @@ -127,39 +157,28 @@ class SignServicePlugin : Plugin { } } + // TODO: extract to factory private fun registerTask( tasks: TaskContainer, - variant: ApplicationVariant, + type: Class, + variant: Variant<*>, taskName: String, serviceUrl: String, - archiveProvider: Provider, - providingTask: TaskProvider<*>, signTokensMap: Map ) { - val buildTypeName = variant.buildType.name + val buildTypeName = requireNonNull(variant.buildType) val token: String? = signTokensMap[buildTypeName] val isSignNeeded: Boolean = token.hasContent() - // сигнал для AGP что мы будем подписывать самостоятельно - // todo не понятно как оно себя ведет с bundle - variant.outputsAreSigned = isSignNeeded - - tasks.register(taskName) { - group = taskGroup - description = "Sign ${variant.name} with in-house service" - - val archiveFile = archiveProvider.get() - // перезаписываем тот же файл - unsignedFileProperty.set(archiveFile) - signedFileProperty.set(archiveFile) + tasks.register(taskName, type) { + it.group = taskGroup + it.description = "Sign ${variant.name} with in-house service" - this.serviceUrl.set(serviceUrl) - tokenProperty.set(token) + it.serviceUrl.set(serviceUrl) + it.tokenProperty.set(token) - onlyIf { isSignNeeded } - }.also { - it.dependsOn(providingTask) + it.onlyIf { isSignNeeded } } } diff --git a/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignerInterface.kt b/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignerInterface.kt index f048cf1c7a..0a004be767 100644 --- a/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignerInterface.kt +++ b/subprojects/gradle/signer/src/main/kotlin/com/avito/plugin/SignerInterface.kt @@ -2,18 +2,32 @@ package com.avito.plugin +import com.android.build.api.component.ComponentIdentity +import com.avito.android.taskName import com.avito.kotlin.dsl.typedNamed import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.TaskProvider internal fun signApkTaskName(variantName: String): String = "signApkViaService${variantName.capitalize()}" +internal fun signApkTaskName(component: ComponentIdentity): String = taskName("signApkViaService", component) + internal fun signBundleTaskName(variantName: String): String = "signBundleViaService${variantName.capitalize()}" -fun TaskContainer.signedApkTaskProvider(variantName: String): TaskProvider { +internal fun signBundleTaskName(component: ComponentIdentity): String = taskName("signBundleViaService", component) + +fun TaskContainer.signedApkTaskProvider(variantName: String): TaskProvider { return typedNamed(signApkTaskName(variantName)) } -fun TaskContainer.signedBundleTaskProvider(variantName: String): TaskProvider { +fun TaskContainer.signedApkTaskProvider(component: ComponentIdentity): TaskProvider { + return typedNamed(signApkTaskName(component)) +} + +fun TaskContainer.signedBundleTaskProvider(variantName: String): TaskProvider { return typedNamed(signBundleTaskName(variantName)) } + +fun TaskContainer.signedBundleTaskProvider(component: ComponentIdentity): TaskProvider { + return typedNamed(signBundleTaskName(component)) +} diff --git a/subprojects/gradle/signer/src/test/kotlin/com/avito/plugin/SignServicePluginTest.kt b/subprojects/gradle/signer/src/test/kotlin/com/avito/plugin/SignServicePluginTest.kt index 1155fa9d63..c4056dd0af 100644 --- a/subprojects/gradle/signer/src/test/kotlin/com/avito/plugin/SignServicePluginTest.kt +++ b/subprojects/gradle/signer/src/test/kotlin/com/avito/plugin/SignServicePluginTest.kt @@ -6,6 +6,7 @@ import com.avito.test.gradle.gradlew import com.avito.test.gradle.module.AndroidAppModule import com.avito.test.http.MockWebServerFactory import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage import okhttp3.mockwebserver.MockResponse import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test @@ -26,13 +27,15 @@ class SignServicePluginTest { @Test fun `plugin apply - fails - configuration without host`() { - generateTestProject(signServiceExtension = """ + generateTestProject( + signServiceExtension = """ signService { // host apk(android.buildTypes.release, "signToken") bundle(android.buildTypes.release, "signToken") } - """.trimIndent()) + """.trimIndent() + ) val result = ciRun( testProjectDir, @@ -71,9 +74,7 @@ class SignServicePluginTest { } /** - * Проверка :android [com.avito.android.bundleFileProvider]. Здесь, т.к. signer - единственный потребительно этого метода - * Путь к bundle не смогли достать из API AGP, строим из пути к APK и уже поймали баг на обновлении до AGP 3.5 - * (к пути добавился buildVariant, которого не было в 3.4) + * TODO: check whether this contract actual or not for a service or CI outputs */ @Test fun `bundle path check`() { @@ -111,6 +112,43 @@ class SignServicePluginTest { ).inOrder() } + @Test + fun `apk signing task - adds signed version to outputs`() { + generateTestProject() + mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody("SIGNED_CONTENT")) + + gradlew( + testProjectDir, + ":app:signApkViaServiceRelease", + "-PsignToken=12345" + ) + + val unsignedApk = File(testProjectDir, "app/build/outputs/apk/release/app-release-unsigned.apk") + assertWithMessage("Preserve original unsigned APK").that(unsignedApk.exists()).isTrue() + + val signedApk = File(testProjectDir, "app/build/outputs/apk/release/app-release.apk") + assertWithMessage("Copy signed APK to outputs. See explanation for this hack inside SignTask") + .that(signedApk.exists()).isTrue() + assertThat(signedApk.readText()).isEqualTo("SIGNED_CONTENT") + } + + @Test + fun `bundle signing task - replaces original output by signed version (HACK)`() { + generateTestProject() + mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody("SIGNED_CONTENT")) + + gradlew( + testProjectDir, + ":app:signBundleViaServiceRelease", + "-PsignToken=12345" + ) + + // See explanation for this hack inside SignTask + val resultArtifact = File(testProjectDir, "app/build/outputs/bundle/release/app-release.aab") + assertThat(resultArtifact.exists()).isTrue() + assertThat(resultArtifact.readText()).isEqualTo("SIGNED_CONTENT") + } + private fun generateTestProject(signServiceExtension: String = defaultExtension) { TestProjectGenerator( modules = listOf(