This topic illustrates how you can use TeamCity build parameters to exchange simple data between configurations of a build chain.
Input and Output Parameters
The Parameters page of build configuration settings lets you switch between input and output parameters. Both of them are manually created name/value pairs. The key difference lies in intended use cases and accessibility settings.
note
The input/output parameters toggle is available only in build configuration settings. For projects, you can create only input parameters.
Input parameters
Input parameters are designed to be consumed by the same configuration that defines them. For example, an input parameter that stores a default branch name and referenced in VCS root settings. This value is of no use for other configurations and thus, should not be visible to them.
Output parameters
Output parameters are configured in one build configuration, but can be accessed by another configuration via a snapshot or artifact dependency. For example, a build configuration that builds a Docker image can write this image name to its parameter. The parameter is later used by a downstream configuration that deploys this image to a registry.
See the sections below for more information on how to create and use output parameters.
Create an Output Parameter
Output parameters can share existing parameters as is, modified parameters, and constants.
outputParams {// Expose predefined parameter as isparam("originConfName","%system.teamcity.buildConfName%")// Expose modified parameter valueparam("buildNumber","Build %\system.build.number%")// Expose input parameter as isparam("name","%existingInputParam%")// Expose static valueparam("number","54")}
The %parameterName% in output parameter value can refer to an existing input parameter, or a dynamically created parameter. For example, the following configuration uses an input "DateFormat" parameter to calculate a value of the "Date" parameter created via the setParameter service message:
import jetbrains.buildServer.configs.kotlin.*import jetbrains.buildServer.configs.kotlin.buildSteps.csharpScript
object CalculateDate :BuildType({
name ="Calculate Date"
params {param("DateFormat","dd/MM/yyyy")// Input parameter}
steps {
csharpScript {
id ="csharpScript"
content ="""
var date = DateTime.Now;// Calculate and add a new 'Date' parameter// Its value is shared by the output parameter
Console.WriteLine("##teamcity[setParameter name='Date' value='"+ date.ToString("%DateFormat%")+"']");""".trimIndent()
tool ="%teamcity.tool.TeamCity.csi.DEFAULT%"}}})
Now you can create an output parameter that shares the "Date" parameter value.
In TeamCity UI, the Add new output parameter dialog has two options: enter the value manually, or choose an existing input parameter. Since our "Date" parameter does not exist until the build step executes, we need to enter the %Date% value by hand.
Otherwise, if "Date" was a regular input parameter, you could choose it from the list:
The output parameter is now accessible by another configuration that depends on the origin configuration. To access a parameter, use the dep.<origin configuration ID>.<output parameter name> syntax.
object PrintDate :BuildType({
name ="Print Date"// Retrieve the 'OutputDate' value and print it
steps {
script {
id ="simpleRunner"
scriptContent ="""echo "The date is ${CalculateDate.depParamRefs["OutputDate"]}""""
}}// An artifact or snapshot dependency is required// to access another configuration's parameter
dependencies {snapshot(CalculateDate){}}})
note
For security reasons, input parameters of the Password type cannot be exposed via output parameters.
Expose All Input Parameters
The Output Parameters tab displays the All parameters are available to other build configurations setting.
If this setting is enabled, all input parameters are accessible by dependent configurations via the dep.<config ID>.<parameter name> syntax. This setting is enabled by default for backward compatibility. However, we strongly recommend you to do the following:
Check all cases of input parameters being used by other configurations.
Expose input parameters you wish to keep sharing by referencing them in new output parameters.
Disable the All parameters are available to other build configurations setting.
Doing so ensures configurations remain easily maintainable: you can edit and remove input parameters as needed, without accidentally breaking downstream configurations that used these parameters via the dep... syntax. In addition, it enhances security by keeping hidden parameters that were never designed to be shared.
Access Output Parameters From Dependent Configurations
Dependent builds can access output parameters of the previous chain builds as dep.<bcID>.<parameter_name>, where bcID is the ID of a source build configuration whose parameter value you need to access.
You can use dep... parameters to access parameters from a configuration even if the current configration has only indirect dependencies. For example, in the A → B → C chain where C depends on B and B depends on A, configuration C can access A's parameters.
The following build configuration builds and pushes a Docker image. The name of this image is written to the DockerImageName parameter.
If this configuration's ID is "ConfigA", builds executed further down the build chain can access the image name as dep.ConfigA.DockerImageName:
docker run -d your.registry/%\dep.ConfigA.DockerImageName%
Override Input Parameters of Preceding Configurations
Add a parameter with the reverse.dep.<build_conf_ID>.<parameter_name> name syntax to override the input <parameter_name> parameter defined in the target configuration that precedes the current configuration.
For example, the following Kotlin code defines a project with three build configurations united in a single build chain (ConfigA → ConfigB → ConfigC). Each build configuration has a chain.ConfigX.param parameter with its custom value. The last configuration has the additional reverse.dep.ChainConfigA.chain.ConfigA.param parameter.
import jetbrains.buildServer.configs.kotlin.*
project {buildType(ChainConfigA)buildType(ChainConfigB)buildType(ChainConfigC)}object ChainConfigA :BuildType({
name ="ChainConfigA"
params {param("chain.ConfigA.param","Config A")}
steps {
script {
scriptContent ="""echo "Parameter value is: %chain.ConfigA.param%""""
}}})object ChainConfigB :BuildType({
name ="ChainConfigB"
params {param("chain.ConfigB.param","Config B")}
steps {
script {
scriptContent ="""echo "Parameter value is: %chain.ConfigB.param%""""
}}
dependencies {snapshot(ChainConfigA){
reuseBuilds = ReuseBuilds.NO
}}})object ChainConfigC :BuildType({
name ="ChainConfigC"
params {param("chain.ConfigC.param","Config C")param("reverse.dep.ChainConfigA.chain.ConfigA.param","Value Overridden in ConfigC")}
steps {
script {
scriptContent ="""echo "Parameter value is: %chain.ConfigC.param%""""
}}
dependencies {snapshot(ChainConfigB){
reuseBuilds = ReuseBuilds.NO
}}})
If you run the ConfigA or the ConfigA → ConfigB sub-chain, the first configuration will report its original parameter value.
# ConfigA build log
Parameter value is: Config A
However, if you run a full build chain that ends with ConfigC, this last configuration will feed ConfigA a custom parameter value.
# ConfigA build log
Parameter value is: Value Overridden in ConfigC
You can use * wildcards in parameter names to the same parameters in multiple preceding configurations. For example, the ConfigC in the following sample has the reverse.dep.ChainConfig*.MyParam parameter, which overrides MyParam in both ConfigA and ConfigB.
If in the ConfigA → ... → ConfigB → ... → ConfigC chain both ConfigB and ConfigC configurations try to override ConfigA's parameter ConfigC has a higher priority since it depends on ConfigB (either directly or through intermediate configurations).
object ChainConfigA :BuildType({
params {param("MyParam","OriginalValue_A")}})object ChainConfigB :BuildType({
params {// Lower priorityparam("reverse.dep.ChainConfigA.MyParam","CustomValue_B")}// Depends on config A
dependencies {snapshot(ChainConfigA){
reuseBuilds = ReuseBuilds.NO
}}})object ChainConfigC :BuildType({
params {// Higher priorityparam("reverse.dep.ChainConfigA.MyParam","CustomValue_C")}// Depends on config B
dependencies {snapshot(ChainConfigB){
reuseBuilds = ReuseBuilds.NO
}}})
However, if ConfigB and ConfigC do not depend on each other, an ambiguity regarding which configuration should have a priority emerges. TeamCity tries to resolve this ambiguity by comparing parameter names and prioritizing a parameter with the most specific build configuration ID.
Highest priority: parameters with no wildcards in build configuration IDs (for example, reverse.dep.ChainConfigA.MyParam).
Medium priority: parameters with partial configuration IDs (for example, reverse.dep.Chain*A.MyParam). The more specific the target configuration ID is, the higher the priority of this parameter. For instance, the ChainConf*A ID has a priority over the Chain*A ID since it is considered more specific.
Lowest priority: parameters with the * wildcard instead of configuration IDs (for example, reverse.dep.*.MyParam).
If all conflicting configurations have similar parameter names and neither of them is a clear winner, TeamCity reports a conflict and creates additional conflict.<build_config_ID>.<parameter_name>=<value> parameters (one for each conflicting configuration).
object ChainConfigA :BuildType({
params {param("MyParam","OriginalValue_A")}})object ChainConfigB :BuildType({
params {// Equal priorityparam("reverse.dep.ChainConfigA.MyParam","CustomValue_B")}// Depends on config A
dependencies {snapshot(ChainConfigA){
reuseBuilds = ReuseBuilds.NO
}}})object ChainConfigC :BuildType({
params {// Equal priorityparam("reverse.dep.ChainConfigA.MyParam","CustomValue_C")}// Depends on config A
dependencies {snapshot(ChainConfigA){
reuseBuilds = ReuseBuilds.NO
}}})// Composite build configuration that runs the entire chainobject ChainABC :BuildType({
type = BuildTypeSettings.Type.COMPOSITE
dependencies {snapshot(ChainConfigB){}snapshot(ChainConfigC){}}})
Other Considerations
The reverse.dep.* parameters are processed on queuing a build where these parameters are defined. Since parameter values should be already known at this stage, these values must be assigned either in the build configuration or in the custom build dialog. Setting the parameter to a value calculated during a build has no effect.
Pushing a new parameter into a build overrides the " Do not run new build if there is a suitable one " snapshot dependency option and may trigger a new build if the parameter is set to a non-default value.
Values of the reverse.dep. parameters are pushed to the dependency builds "as is", without reference resolution. %-references, if any, will be resolved in the destination (target) build's scope.