BuildMaster Documentation

Writing a Variable Function

BuildMaster's variable-replacement functionality can be extended by writing a custom function using any .NET language.

How to Write a Variable Function in C#

A variable function works just like a variable in BuildMaster, except it may optionally take arguments, and its value cannot be set. For example, one of the built-in functions is MajorVersionNumber; it can be invoked like $MajorVersionNumber to return the major version component of the current release number, or it can be invoked with an explicit parameter like $MajorVersionNumber("5.0.2"). A custom function works exactly the same way, only using custom logic supplied in an extension.

Below will guide you through creating a new function that returns name of an application's issue tracking provider. To start, create a new class named IssueTrackerNameVariableFunction that inherits ScalarVariableFunctionBase:

public class IssueTrackerNameVariableFunction : ScalarVariableFunctionBase
  protected override object EvaluateScalar(IGenericBuildMasterContext context)
    // TODO: return result of function evaluation

For scalar variables, the EvaluateScalar method is invoked by BuildMaster at execution time. The context parameter contains various BuildMaster-related information, such as the current application ID, release number, build number, and so on. See the IGenericBuildMasterContext interface for more information regarding its available properties.

Arguments supplied to the function are automatically bound to properties decorated with the VariableFunctionParameterAttribute attribute. In this tutorial, we allow one optional argument containing an application ID. The following code will look up the appropriate issue tracker name and return it:

[Description("The ID of the application whose name will be returned. If empty, the application ID "
           + "in the current context will be used.")]
[VariableFunctionParameter(0, Optional = true)]
public int? ApplicationId { get; set; }

protected override object EvaluateScalar(IGenericBuildMasterContext context)
    int? applicationId = this.ApplicationId ?? context.ApplicationId;
    if (applicationId == null || applicationId <= 0)
        return "INVALID APPLICATION ID";

    var application = DB.Applications_GetApplication(applicationId)

    if (application == null)
        return "INVALID APPLICATION ID";

    if (application.IssueTracking_Provider_Id == null)
        return string.Empty;

    var issueTracker = DB.Providers_GetProvider(application.IssueTracking_Provider_Id);

    return issueTracker.Provider_Name;

This fairly simple code uses the application ID from the first argument, if it is supplied. Otherwise, it uses the application ID in context. Then, it looks up the application in BuildMaster, and finally looks up the issue tracking provider as well and returns its name.

The function is now implemented, but we need to add an attribute to tell BuildMaster how this should be invoked. We can do this by adding a VariableFunctionProperties attribute to the class:

[Description("Returns the name of the issue tracker associated with the application in the current scope.")]
    Scope = VariableFunctionScope.Application,
    Category = "Issue Tracking")]
public class IssueTrackerNameVariableFunction : ScalarVariableFunctionBase

The ScriptAliasAttribute specifies the name of the function for use in OtterScript, and the DescriptionAttribute documents what the function returns. The Scope value helps BuildMaster understand where the function is meant to be used. The optional Category value is used for informational purposes only, particularly the grouping on the Functions documentation page. Since this action has to do with issue tracking, we'll just use that as the category.

This is enough information for BuildMaster. Once compiled as part of an extension and added to the extension library, this function can now be invoked like any of the built in functions.

A Note on Input Validation

It is up to you to determine how to perform input validation. In this tutorial, we've just returned OBNOXIOUS STRING LITERALS that would usually be easy to notice. You may also choose to throw an exception, which would halt whatever was in progress and log an error message, or you may simply use a default value instead of the bad input - use whatever works best for your use case.