Output from multiple instances of the same step

Is there a good pattern for differentiating between outputs of multiple instances of the same step? Iā€™m working on a step right now to extract the version for any given node.js package.json so I was trying to figure out how to make it more generic/modular. Right now it uses envman to export a single output variable. But what if you had more than one package you wanted to inspect? Is there a good way to hang on to seperate outputs for each of them?

I had a few ideas but Iā€™m not really in love with any of them:

  1. The exported envvar could have a configurable name, probably using some nasty bash eval hack. This would make the outputs defined in the step.yml misleading at best, assuming it even works.
  2. The exported envvar could be a serialized data structure that is appended to on subsequent runs (My first thought was to use a bash array.) But this would not work very well for the purpose of having envvars that can be easily routed as inputs to other scripts.
1 Like

Great question @eliot_pear!

There are two things worth to discuss here:

  1. How to expose a list of outputs from a Step, and consume that in other steps
  2. How to run the same step multiple times, and have access to the output of all the same steps, instead of overwriting each others

How to expose a list of outputs from a Step

For the first point we think this should be the official way to do it: https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md#inputs-which-can-accept-a-list-of-values

TL;DR;

You should postfix the input ID with _list (e.g. input_path_list), and expect the values to be provided as a pipe character separated list (e.g. first value|second value)

Weā€™ll soon start to test this solution through our core steps, so far this solution seems to be the best way to handle lists of outputs, e.g. if a Gradle Runner step generates more than one .apk file.

How to reference outputs of separate steps which expose outputs with the same environment variable key

The solution for this right now, which works but not exactly a clean solution, is to use a Script step, right after the step which generated the output which would be overwritten later, and in the Script step ā€œcopyā€ the value to a new environment variable. Docs: Environment Variables - Bitrise Docs

This could be released as a new step too, see: [Step] "bridge" an environment variable - assign the value of one Step's output to another Environment Variable

What we plan to do, is to add this as a built in feature in the Bitrise CLI / bitrise.yml . In short, you could do something like what you described:

but instead of doing any eval hack, and hopefully without making this ā€œmisleadingā€, weā€™d propose a new item/syntax for step outputs. In bitrise.yml right now you canā€™t overwrite outputs (it makes sense, as you canā€™t change how a step generates an output), but we would add an ā€œaliasā€ or ā€œnameā€ syntax, so that you could define an alternative name for the environment variable.

E.g.:

- git-clone:
    inputs: []
    outputs:
    - GIT_CLONE_COMMIT_HASH: SAVE_IT_INTO_THIS_ENV_VAR

This would indicate for the Bitrise CLI that you want to store the GIT_CLONE_COMMIT_HASH output of the Git Clone step into the SAVE_IT_INTO_THIS_ENV_VAR environment variable, instead of into GIT_CLONE_COMMIT_HASH. Of course if you donā€™t provide an output item for the step, then GIT_CLONE_COMMIT_HASH will be generated and it wonā€™t be modified, like the way it works today.

This would be a pretty similar solution to what I described above as ā€œThe solution for this right nowā€, but built into the Bitrise CLI and into the bitrise.yml format specs, so that you donā€™t have to use a separate step for this.

Whether this is a copy or a rename of the GIT_CLONE_COMMIT_HASH env var is still up for discussion (whether GIT_CLONE_COMMIT_HASH should still be populated in this case, or only SAVE_IT_INTO_THIS_ENV_VAR). Personally I think ā€œrenameā€ should be enough, if you define an alternative name thereā€™s no need to keep the value in the original output as well.

WDYT @eliot_pear?

1 Like

I like the solution of incorporating this functionality into the bitrise.yml. ā€œRenameā€ seems like the best option here as the final value of the original exported variable might be misleading as it will be dependent on the order of which step executed last. I also like that this solution doesnā€™t require additional overhead on the part of the step, and will work without having to update existing steps.

In addition it might be nice to get a warning in the logs if envman tries to set an envvar that is already set as a clue that the output of a step should be re-routed by the consumer. (perhaps with a way for the script to configure envman to suppress the warning in case this behavior is by design.)

Also, if the idea is to support multiple steps that can interact with ā€œlistā€ inputs and outputs, perhaps a tool can be added to the bitrise cli to ease interaction with these rather than expecting each script to roll its own. I donā€™t have a use case for this right now but something to think about. This is more of a problem in bash as its collection handling is rather primitive, Iā€™d expect higher-level languages to mostly have easy-to-use split and join functions. I guess if this were only needed by bash scripts, this could be as simple as a versioned, shared library which is part of the cli environment that can be source'd by any script and have a function that can be called to parse a list string into a bash array, as well as one that will convert a bash array back into a string. I took a crack at writing these routines myself, feel free to use them.

It would also be nice to put some other common helper functions into this library such as validate_required_input, echo_fail, etc. rather than having to copy-paste them into every step script.

1 Like

I agree, I think weā€™ll go with ā€œrenameā€ and see if thereā€™s any use case for ā€œcopyā€.

Good idea! ;)[quote=ā€œeliot_pear, post:3, topic:538ā€]
if the idea is to support multiple steps that can interact with ā€œlistā€ inputs and outputs, perhaps a tool can be added to the bitrise cli to ease interaction
[/quote]

We thought about this, but so far we couldnā€™t find a better solution. If you have any idea please let us know![quote=ā€œeliot_pear, post:3, topic:538ā€]
This is more of a problem in bash as its collection handling is rather primitive, Iā€™d expect higher-level languages to mostly have easy-to-use split and join functions.
[/quote]

The solution for this right now is to use a different language for the step (preferably Go as it has the best CLI support), which has proper support for parsing and processing these data. Iā€™d say that if you have to process a list of inputs then youā€™ll eventually end up writing the step in a more advanced language than Bash anyway, especially if you have to handle errors, retries, etc. For example, while you might be able to write a simple Bash step for uploading an app to e.g. HockeyApp, if you have to do that for a list of inputs and preferably handle upload errors and retry properly, that can be quite a challenge in Bash. But again, if you have an idea, weā€™re always happy to discuss :wink:

We have that, for Go :wink: - GitHub - bitrise-io/go-utils: Common, utility packages for Go

1 Like

@viktorbenei One step ahead of you on the bash implementationā€¦ did you miss the link on my previous post? :slight_smile:

Certainly bash is not the right hammer for certain nails (especially for more complex scenarios, ex. requiring parallelism like you mentioned above) but I think shell scripts also have unrivaled expressiveness for interacting with other command-line tools. I think itā€™s a good idea to provide some step interop facilities and then allow the implementor to decide which language is best for the job.

My pure bash 3.x+ implementation is below, feel free to use it (although if you do take my suggestion, Iā€™d reccomend giving the ā€˜bash libraryā€™ a versioned path/name so that new versions could be released without having to worry about maintaining backwards compatibility):

1 Like

Ohh, I definitely agree with you! The only thing is, why we donā€™t plan to provide a bash utility library (but that should not stop anyone else to do so!!) is that we could not properly maintain it (as we always use Go for more complex steps, and we do have a common Go library actively maintained by us), and that we do think that in most more complex cases Bash is simply not the right tool (e.g. error handling is way too complicated once your bash script grows over a couple of tens of lines of bash code).

But again, this should not stop anyone from creating a utility bash lib/code!

Thanks for sharing your util code @eliot_pear (looks pretty neat btw, I donā€™t think I could have written it the way you did - you most likely have more Bash experience than me ;)), Iā€™m sure itā€™ll help other step devs!

Ohh, one more thing: if you want to feel free to create a ā€œproperā€ repository for these Bash util scripts, we can highlight it in the docs & here on discuss.bitrise.io for other step devs if you want to. Just make sure itā€™s simple to integrate into a new step (quick guide about how to include it).

@viktorbenei Thanks! Iā€™m curious if you have thoughts on how such a lib could be included in the step without violating the ā€œDo not use submodules, or require any other resource downloaded on-demandā€ guideline. People could just copy this file into their step repo, but how will they know there is an update? And is the update process to just copy-paste the whole library again into their step repo?

Thatā€™s why I was thinking it might make sense to be a part of the CLI environment and function somewhat like system-provided C headers. So the script could do something like source "$BITRISE_CLI_SHELL_LIBS/latest/bash/list.sh" or source "$BITRISE_CLI_SHELL_LIBS/0.0.1/bash/list.sh". (Not sure if having a ā€œlatestā€ symlink would be a great idea though as new bitrise cli releases could cause regressions on existing steps.)

We usually use our depman tool for this. Itā€™s really really simple, a revision is long overdue, but it can do this perfectly. Basically you depman init, which generates a dependency definition file. You fill that out by specifying the git clone URLs and the relative path where it should be stored, and any time you want to update you run depman update, which will git clone the latest version from the repos and move the files (without .git) to the specified relative path.

1 Like

Iā€™m happy to announce that this is now available in the latest CLI version, v1.6.0!! :wink: Finally, I personally waited for this for a really long time now, but prioritiesā€¦

Anyway, v1.6.0 was just released, it can now be installed locally, and will be deployed on the bitrise.io VMs during the next stack updates this weekend, as usual :slight_smile:

1 Like