Create custom bitrise step

//Create the Step with the Bitrise Step pluginmkdir my_step_dir 
cd my_step_dir
bitrise :step create
title: Change Android versionCode and add versionName suffix
summary: Updates the Android versionCode and versionName in your project's `build.gradle` file.
description: Modifies the version information of your Android app by updating versionCode and versionName attributes in your project's `build.gradle` file before you'd publish your app to Google Play Store.

website: ___
source_code_url: ___(your git repo link)
support_url: ___
- osx-10.10
- ubuntu-16.04
- android
- react-native
- cordova
- ionic
- flutter
- utility
is_requires_admin_user: true
is_always_run: false
is_skippable: false
run_if: ""
package_name: of step___
- build_gradle_path: $BITRISE_SOURCE_DIR/app/build.gradle
title: Path to the build.gradle file
summary: Path to the build.gradle file shows the versionCode and versionName settings.
is_required: true
- version_name_seperator:
title: versionName and suffix seperator
summary: |-
Add a seperattor to version name andd suffix. Default "-".
description: |-
Add a seperattor to version name andd suffix.
Default "-"
- version_name_suffix:
title: versionName suffix
summary: |-
Add suffix to version name.
description: |-
Add suffix to version name.
- new_version_code: $BITRISE_BUILD_NUMBER
title: New versionCode
summary: |-
New versionCode to set.
description: |-
New versionCode to set.
Specify a positive integer value, such as `1`.
The greatest value Google Play allows for versionCode is 2100000000.
Leave this input empty so that versionCode remains unchanged.
- version_code_offset:
title: versionCode Offset
summary: |-
Offset value to add to `New versionCode`.
description: |-
Offset value to add to `New versionCode`, for example: `1`.
Leave this input empty if you want the exact value you set in `New versionCode` input.
title: Final Android versionName in build.gradle file
title: Final Android versionCode in build.gradle file
package mainimport (
const (
versionCodeRegexPattern = `^versionCode(?:\s|=)+(.*?)(?:\s|\/\/|$)`
versionNameRegexPattern = `^versionName(?:=|\s)+(.*?)(?:\s|\/\/|$)`
type config struct {
BuildGradlePth string `env:"build_gradle_path,file"`
VersionNameSep string `env:"version_name_seperator"`
VersionNameSuffix string `env:"version_name_suffix"`
NewVersionCode int `env:"new_version_code,range]0..2100000000]"`
VersionCodeOffset int `env:"version_code_offset"`
type updateFn func(line string, lineNum int, matches []string) stringfunc findAndUpdate(reader io.Reader, update map[*regexp.Regexp]updateFn) (string, error) {
scanner := bufio.NewScanner(reader)
var updatedLines []string
for lineNum := 0; scanner.Scan(); lineNum++ {
line := scanner.Text()
updated := false
for re, fn := range update {
if match := re.FindStringSubmatch(strings.TrimSpace(line)); len(match) == 2 {
if updatedLine := fn(line, lineNum, match); updatedLine != "" {
updatedLines = append(updatedLines, updatedLine)
updated = true
if !updated {
updatedLines = append(updatedLines, line)
return strings.Join(updatedLines, "\n"), scanner.Err()
func exportOutputs(outputs map[string]string) error {
for envKey, envValue := range outputs {
cmd := command.New("envman", "add", "--key", envKey, "--value", envValue)
if err := cmd.Run(); err != nil {
return err
return nil
func failf(format string, v ...interface{}) {
log.Errorf(format, v...)
// BuildGradleVersionUpdater updates versionName and versionCode in the given build.gradle file.
type BuildGradleVersionUpdater struct {
buildGradleReader io.Reader
// NewBuildGradleVersionUpdater constructs a new BuildGradleVersionUpdater.
func NewBuildGradleVersionUpdater(buildGradleReader io.Reader) BuildGradleVersionUpdater {
return BuildGradleVersionUpdater{buildGradleReader: buildGradleReader}
// UpdateResult stors the result of the version update.
type UpdateResult struct {
NewContent string
FinalVersionCode string
FinalVersionName string
RealVersionName string
UpdatedVersionCodes int
UpdatedVersionNames int
// UpdateVersion executes the version updates.
func (u BuildGradleVersionUpdater) UpdateVersion(newVersionCode, versionCodeOffset int, versionNameSep, versionNameSuffix string) (UpdateResult, error) {
res := UpdateResult{}
var err error
res.NewContent, err = findAndUpdate(u.buildGradleReader, map[*regexp.Regexp]updateFn{
regexp.MustCompile(versionCodeRegexPattern): func(line string, lineNum int, match []string) string {
oldVersionCode := match[1]
res.FinalVersionCode = oldVersionCode
updatedLine := ""
if newVersionCode > 0 {
res.FinalVersionCode = strconv.Itoa(newVersionCode + versionCodeOffset)
updatedLine = strings.Replace(line, oldVersionCode, res.FinalVersionCode, -1)
log.Printf("updating line (%d): %s -> %s", lineNum, line, updatedLine)
return updatedLine
regexp.MustCompile(versionNameRegexPattern): func(line string, lineNum int, match []string) string {
oldVersionName := match[1]
res.FinalVersionName = oldVersionName
res.RealVersionName = oldVersionName
updatedLine := ""
if versionNameSuffix != "" {
versionName := ""
if versionNameSep != "" {
versionName = oldVersionName + versionNameSep + versionNameSuffix
} else {
versionName = oldVersionName + "-" + versionNameSuffix
quotedVersionName := `"` + strings.Replace(versionName, "\"", "", -1) + `"`
log.Printf("final version name - %s", quotedVersionName)
res.FinalVersionName = quotedVersionName
res.RealVersionName = oldVersionName
updatedLine = strings.Replace(line, oldVersionName, res.FinalVersionName, -1)
log.Printf("updating line (%d): %s -> %s", lineNum, line, updatedLine)
return updatedLine
if err != nil {
return UpdateResult{}, err
return res, nil
func main() {
var cfg config
if err := stepconf.Parse(&cfg); err != nil {
failf("Issue with input: %s", err)
if cfg.VersionNameSuffix == "" && cfg.NewVersionCode == 0 {
failf("Neither NewVersionCode nor VersionNameSuffix are provided, however one of them is required.")
// find versionName & versionCode with regexp
log.Infof("Updating versionName and versionCode in: %s", cfg.BuildGradlePth)
f, err := os.Open(cfg.BuildGradlePth)
if err != nil {
failf("Failed to read build.gradle file, error: %s", err)
versionUpdater := NewBuildGradleVersionUpdater(f)
res, err := versionUpdater.UpdateVersion(cfg.NewVersionCode, cfg.VersionCodeOffset, cfg.VersionNameSep, cfg.VersionNameSuffix)
if err != nil {
failf("Failed to update versions: %s", err)
// export outputs
if err := exportOutputs(map[string]string{
"ANDROID_VERSION_NAME": res.RealVersionName,
"ANDROID_VERSION_CODE": res.FinalVersionCode,
}); err != nil {
failf("Failed to export outputs, error: %s", err)
if err := fileutil.WriteStringToFile(cfg.BuildGradlePth, res.NewContent); err != nil {
failf("Failed to write build.gradle file, error: %s", err)
log.Donef("%d versionCode updated", res.UpdatedVersionCodes)
log.Donef("%d versionName updated", res.UpdatedVersionNames)
- activate-ssh-key@3.1.1:
run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}'
- git-clone@4.0.5: {}
- cache-pull@2.0.1: {}
- install-missing-android-tools@2.1.1: {}
- git::
- version_name_seperator: ''
- version_name_suffix: "$BITRISE_BUILD_NUMBER"
- new_version_code: "$BITRISE_BUILD_NUMBER"
- gradle-runner@1.8.3:
... // other steps
  • git:: source type can be used for not-yet-published or work-in-progress states of a step
  • Get the clone url from your repository.
  • Instead of version, we can use branch name or tag
- git::{username}/{stepName}.git@BRANCH-OR-TAG:




Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Using static code analysis tool in Android Studio

How to convert attach file to base64 in Android?

Android Navigation: Multiple Back Stacks Support

Flutter Overflow Error: Single Page View

Learn Motion Layout in Android Using the Twitter Splash Screen

Jetpack compose custom rating bar in android 2022

How a Wallpaper Could Crash Your Android Phone!

onActivityResult() for Fragments!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Akshay Kale

Akshay Kale

More from Medium

Working with Custom Layout in HarmonyOS

Harmony OS: How to release an app?

Complete Guide — In App Update for Android Apps — Google Playstore

Local Databases in Android