Skip to content

CI steps plugin#

Warning

Plugin wasn't tested outside Avito yet, so expect difficulties, or even blockers. However, if you interested, please contact us.

Plugin creates chains of tasks for CI, encapsulating it under single gradle task.

Getting started#

Apply the plugin in the app's build.gradle file:

plugins {
    id("com.avito.android.cd")
}

The plugin can be applied to a root project or any module.

Setup plugins

In the settings.gradle:

pluginManagement {
    repositories {
        mavenCentral()
    }
    resolutionStrategy {
        eachPlugin {
            String pluginId = requested.id.id
            if (pluginId.startsWith("com.avito.android")) {
                def artifact = pluginId.replace("com.avito.android.", "")
                useModule("com.avito.android:$artifact:$avitoToolsVersion")
            }
        }
    }
}

avitoToolsVersion could be exact version, or property in project's gradle.properties. The latest version could be found on project's release page.

Builds#

First, name your chain:

builds {
    register("myChain") {
 
       //optional description for generated task
       taskDescription.set("This chain does something useful")
    }
}
builds {
    myChain {

       //optional description for generated task
       taskDescription.set("This chain does something useful")
    }
}

Avito example chains#

  • localCheck - compilation checks for local run
  • prCheck - as fast as possible checks for Pull Request
  • fullCheck - as full as possible checks to be run after merges, non-blocking, could be slow
  • release - chain to release our app

Steps#

Step is a declaration to run some logic. It works inside a chain:

build {
    register("prCheck") { // <--- chain

        unitTests {} // <--- step
        uiTests {}

        //optional description for generated task
        taskDescription.set("This chain does something useful")
    }
}
build {
    fastCheck { // <--- chain

        unitTests {} // <--- step
        uiTests {}

        //optional description for generated task
        taskDescription.set("This chain does something useful")
    }
}

Now when you invoke ./gradlew fastCheck gradle will run unitTests, uiTests of corresponding project

Built-in steps#

UI tests#

Runs instrumentation tests.

uiTests {
  configurations("configurationName") // list of instrumentation configuration to depends on
  sendStatistics = false // by default
  suppressFailures = false // by default
  useImpactAnalysis = true // by default
  suppressFlaky = false // by default. [игнорирование падений FlakyTest](../test/FlakyAnnotation.md).
}
uiTests {
  configurations = ["configurationName"] // list of instrumentation configuration to depends on
  sendStatistics = false // by default
  suppressFailures = false // by default
  useImpactAnalysis = true // by default
  suppressFlaky = false // by default. [игнорирование падений FlakyTest](../test/FlakyAnnotation.md).
}

Compile UI tests#

Compile instrumentation tests. It is helpful in local development.

compileUiTests {}
compileUiTests {}

Unit tests#

Run unit tests.

unitTests {}
unitTests {}

Upload to QApps#

Disclaimer

The text below contains Avito specific details

Upload artifacts to QApps (internal system)

artifacts {
    apk("debugApk", ...)
}
uploadToQapps {
    artifacts = setOf("debugApk")
}
artifacts {
    apk("debugApk", ...)
}
uploadToQapps {
    artifacts = ["debugApk"]
}

Upload to Artifactory#

Upload artifacts to Artifactory.

artifacts {
    file("myReport", "${project.buildDir}/reports/my_report.json")
}
uploadToArtifactory {
    artifacts = setOf("myReport")
}
artifacts {
    file("myReport", "${project.buildDir}/reports/my_report.json")
}
uploadToArtifactory {
    artifacts = ["myReport"]
}

Upload to Prosector#

Disclaimer

The text below contains Avito specific details

Upload artifacts to Prosector (internal).

artifacts {
    apk("debugApk", ...)
}
uploadToProsector {
    artifacts = setOf("debugApk")
}
artifacts {
    apk("debugApk", ...)
}
uploadToProsector {
    artifacts = ["debugApk"]
}

Upload build results#

Disclaimer

The text below contains Avito specific details

Upload all build results to a deploy service.

uploadBuildResult {
    uiTestConfiguration = "regression" // instrumentation configuration
}
uploadBuildResult {
    uiTestConfiguration = "regression" // instrumentation configuration
}

Deploy to Google Play#

Disclaimer

The text below contains Avito specific details

Deploy to Google play.

deploy {}
deploy {}

Mark report as source of truth for TMS#

See Test case in code

Configuration checks#

Disclaimer

The text below contains Avito specific details

Checks a repository configuration. See :build-script-test for details.

configuration {}
configuration {}

Custom steps#

If you need to run a simple task:

customTask("myStep") {
    tasksPredicate = TasksPredicate.byName("myTask")
}

Overriding steps#

It's useful when you want to setup default settings and override them per-project.
Overriding is explicit to avoid accidental changes.

// Root project:
subprojects {
    plugins.withType<CiStepsPlugin> {
        extensions.configure<NamedDomainObjectContainer<BuildStepListExtension>> {
            register("release") {
                uiTests {
                    configurations("configurationName")
                    suppressFailures = false
                }
            }
        }
    }
}

// Application:

builds {
    getByName("release") {
        overrideStep<UiTestCheck> {
            suppressFailures = true
        }
    }
}

Using impact analysis in step#

Step can use Impact analysis. It is enabled by default.

fastCheck {
    uiTests {
    }
}
fastCheck {
    uiTests {
    }
}

Suppressing errors in step#

In different scenarios steps could fail whole build, some can be configured not to.

fastCheck {
    uiTests { 
        suppressFailures = false 
    }
}

release {
    uiTests { 
        suppressFailures = true 
    }
}
fastCheck {
    uiTests { 
        suppressFailures = false 
    }
}

release {
    uiTests { 
        suppressFailures = true 
    }
}

Collecting artifacts#

Artifacts that planned to be used(uploaded somewhere) must be registered:

artifacts {
   file("lintReport", "${project.buildDir}/reports/lint-results-release.html")
}
artifacts {
   file("lintReport", "${project.buildDir}/reports/lint-results-release.html")
}

There are different types of artifacts:

  • apk - gets apk by buildType and checks packageName and signature
  • bundle - gets bundle by buildType and checks packageName and signature
  • mapping - gets r8 mapping by buildType and checks availability
  • file - gets any file by path and checks availability
import com.avito.cd.BuildVariant.RELEASE

val releaseSha1 = "my sha" // it's public info, so safe to share

artifacts {
   apk("releaseApk", RELEASE, "com.avito.android", "${project.buildDir}/outputs/apk/release/avito.apk") { signature = releaseSha1 }
   bundle("releaseBundle", RELEASE, "com.avito.android", "${project.buildDir}/outputs/bundle/release/avito.aab") { signature = releaseSha1 }
   mapping("releaseMapping", RELEASE, "${project.buildDir}/outputs/mapping/release/mapping.txt")
   file("featureTogglesJson", "${project.buildDir}/reports/feature_toggles.json")
}
def releaseSha1 = "my sha" // it's public info, so safe to share

artifacts {
   apk("releaseApk", RELEASE, "com.avito.android", "${project.buildDir}/outputs/apk/release/avito.apk") { signature = releaseSha1 }
   bundle("releaseBundle", RELEASE, "com.avito.android", "${project.buildDir}/outputs/bundle/release/avito.aab") { signature = releaseSha1 }
   mapping("releaseMapping", RELEASE, "${project.buildDir}/outputs/mapping/release/mapping.txt")
   file("featureTogglesJson", "${project.buildDir}/reports/feature_toggles.json")
}

The first argument is a key for upload steps.