Background is a Continuous Integration and Delivery (CI/CD) Platform as a Service (PaaS) with the main focus on mobile app development. . However, integrations for niche and/or brand new tools may not be available out of the box immediately. Usually, the fastest solution is to write an ad-hoc shell script. Nonetheless, scripts are also the hardest to reuse in multiple projects. Bitrise.io Bitrise provides ready to use integrations with popular, widely used tools, like Gradle , Xamarin or Xcode Moreover, shell scripts are often not suitable for complex actions. Is there a better way? Of course! . In this article, we will show you how to create your own Bitrise step and how to publish it so everyone can make use of it Why another article? Instructions involving creating steps are available on the Bitrise as well as dedicated (guest) . Nevertheless, in this article, . DevCenter blogpost we will focus on programming in Go which is the main language used by Bitrise. It is also preferred for non-trivial steps The problem Flutter is one of the cross-platform mobile application development SDKs. More about it you can read in the article: . At DroidsOnRoids we have recently started using Flutter for mobile app development. Flutter in Mobile App Development — Pros & Risks for App Owners We started using Flutter, but there was no Bitrise step available. So we decided to create one ourselves! 1. Preparation You can use your favorite editor/IDE to work with source files. If you are familiar with Android Studio or other IDEs from JetBrains you may be interested in . Before we start coding we need to prepare our environment. GoLand Apart from that, . Just and set it up by invoking . Note that you need a Linux or Mac machine. CLI for Windows is not available. we need a Bitrise CLI install bitrise setup Finally, . You can install it using your package manager (apt-get, Homebrew etc.) or download it from the . we will need Go tools project site Next, we can actually create a step using . Note the colon, it is needed because here denotes a plugin, not a command. Keep in mind that we have to select as a toolkit. Creation process is interactive and you will be asked to enter the details step by step. Just like this: bitrise :step create :step go 2. Step properties Now we have a working step skeleton, most of the properties are set to reasonable default values. Nevertheless, we need to adjust a few of them. Firstly we can set to because executing Flutter commands does not require admin/superuser permissions. Next we can add dependencies. Each dependency here is an (on Linux) or (on MacOS) package. is_requires_admin_user false apt-get Homebrew How to find out which dependencies are needed? A good starting point is a documentation of given tool. For example Flutter has a chapter in their docs which contains a list of required system components. is also available. However, it turns out that there is one more unlisted requirement — . Get Started: Install Linux version libglu1-mesa Finally our section in file should look like this: deps step.yml 3. Inputs Virtually all the Bitrise steps need to be somehow configurable by the users. In case of Flutter, they may want to e.g. and/or . choose which Flutter commands they want to run test build Moreover, it will be useful to be able to . Optionally it may default to the current one. specify exact Flutter version Finally, we may also . The easiest way to do that is to establish reasonable default value but allow users to change it when they need to. Complete inputs section of Flutter step looks like this: support cases when Flutter project is located somewhere else than repository root directory All the inputs have names: , and respectively. Right after the name, each input contains a default value which will be used if users don't set it explicitly in their configuration. - . It is by Bitrise CLI. At runtime, it will be by actual value. In order for such substitution to work, the flag needs to be enabled. Inputs section should look like this in graphical workflow editor: version working_dir commands Note that one of them is not a hardcoded text but an environment variable $BITRISE_SOURCE_DIR exposed substituted is_expand 4. Implementation Our step will be written in language. It’s used in virtually all non-trivial, official steps. Moreover, Bitrise provides — a collection of functions useful in Continuous Integration, so . Go go-utils there is no need to implement everything from scratch and we can focus on business logic ✔️ Golang basics This article is not meant to be tutorial about programming in Go. It will only explain the most important things useful during step development. I also assume that you have basic programming knowledge, so I will not explain here what is a string or nil. You can use the official to quickly explore Go language basics. tour Error handling In Golang there are no exceptions which can be thrown to interrupt current flow. If given function invocation can fail it has as the last return value (functions can return multiple values). We need to check if error is not to determine if operation succeeded. If the error is fatal it is usually propagated to the function where we can print it to the log and exit with non-zero code. error nil main Keep in mind that . Go lint will complain about ignored errors. errors should not be swallowed but logged or returned to the caller Dependencies At the time of writing, there is no standard, built-in dependency management system in Go. Bitrise uses — . Dep is not shipped with Go. It has to be separately. dep an official experiment ready for production use installed Note that files generated by dep need to be checked into Version Control System. Apart from configuration files, there is also source code of all the dependencies. ✔️ Configuration Step configuration comes from the environment. All the aforementioned become environment variables. Note the snake_case in names, it’s a on Bitrise. Pipe character ( ) used as a multiple input values separator is also guided by . inputs convention | convention Parsing environment variables into objects usable from Go code can be done using . We can just declare the structure containing configuration parameters and let the parse it: go-steputils stepconf Note that structure name starts with uppercase. It is needed if the structure is accessed from another go files. Comment with ellipsis is only used to make lint happy. Each structure field has a tag with corresponding environment variable name. A tag can also contain properties e.g. whether given field is required or it should represent a path to the directory. Parsing will fail if conditions are not met. In case of invalid configuration, we need to exit with non-zero code. ✔️ Flutter logic Rest of the source code represents actions specific to Flutter invocation: Ensure that Android SDK is up to date (only if it is present) — Flutter requires at least 26.0.0 Download and extract Flutter SDK (destination path is OS-specific) Execute supplied Flutter commands The full source code is available on . GitHub ✔️ Hints Operations on files, directories, paths, commands invocation, printing logs etc. are commonly used in CI so they can likely exist somewhere in Bitrise open-source repos like or . Before implementing something from scratch check first if something similar does not exist either in Go standard library or in external libraries. bitrise-tools go-utils To add a dependency on external library invoke: from terminal. Where is a value placed in import declarations e.g. . dep ensure -add <import path> <import path> github.com/bitrise-io/go-utils/pathutil If you need to perform cleanup after some operation whenever it succeeded or not use a . It is similar to block in Java/Kotlin. However, you should also not ignore errors but log them: defer statement finally Note that you cannot propagate error from a deferred function. ✔️ Tests & static code analysis According to each step should have workflow. Usually it consists of several steps: StepLib pull request template test Unit tests Go linter — additional static code analysis tool errcheck Integration tests In StepLib there are steps for all aforementioned kinds of tests. Here is how test workflow can look like: and steps are generated automatically during step creation. Integration tests often need some reasonable inputs. If some of that input is non-public e.g. it’s API key/token etc. you can define it as a . Unit test file names should have suffix in order to be recognized properly. Unit tests on Bitrise usually use framework for assertions. Here is the simple unit test example: Switch working dir Step test secret _test testify Steps like or can be useful in that matter. Keep in mind that if a step is applicable for all the platforms (Android and iOS) like Flutter you should test it on more than one Bitrise stack . Trigger Bitrise workflow Bitrise Start Build 5. The finishing touches If your step is ready you can request to publish it to StepLib. To do this you have to first fork the repo and set environment variable in to URL of that fork. You also need a tag (e.g. ) on the repo of your step ( the StepLib fork). If all those requirements are met you can invoke . Note that it will also run audit required by StepLib checklist so you don't need to execute any other commands. StepLib MY_STEPLIB_REPO_FORK_GIT_URL bitrise.yml semver 0.0.1 not bitrise run share-this-step Now you can create a pull request from your StepLib fork to the upstream and wait for review by Bitrise team. Step may be reviewed even in few minutes 😊: If you are releasing an update to already existing step you should also create on GitHub repo of the step. It is the source of the . release notes StepLib changelog Wrap up I hope that my article will help you to create and publish your own Bitrise steps. As you can see above, it’s not very difficult. You need to remember that Bitrise offers $25 for step contributors. discount Resources Step development guideline Bitrise devcenter Go language playground Originally published at iOS & Android Mobile App Development Company — Droids On Roids — Poland .