KBEC-00018 - Managing environment variables across steps

Article ID:360032832632
4 minute readKnowledge base

Description

Each step in ElectricCommander runs in a new process that starts with a clean environment. If Step 1 sets environment variables, they are not available to Step 2, even if the two steps run on the same resource. This solution saves environment variables created or changed in one step, then reloads them in a subsequent step.

If you have a known set of environment variables you want to set for each step, load those variables in each step. However, if your overall procedure starts by spending a significant amount of time calculating a set of variables for the procedure and you want to make sure the calculation is done once only for the entire procedure, use this solution.

Solution

This solution consists of two or more steps, plus a Perl script (provided below). The first step calculates the environment and saves the result—​calling your own environment setup, and also calling a Perl script to "instrument" your system before and after setup. Next, you run a "custom postprocessor" that calls the same Perl script again, which analyzes the "instrumented output" from the step and saves the differences to a script.

In the second step, and all following steps, load the "differences script" at the start of the step to re-setup environment variables.

Create a setup step

The two calls to the Perl script in the "Command:" box will print a "before" and "after" snapshot of your environment to the stepLog. Make sure the steps you use to set your environment will cause the variables to change persistently. For example, in a UNIX shell environment, all variables should be exported.

YOUR_SCRIPT_NAME.bat is the name of the script file that will contain the commands to re-establish the environment. The script will be created in the project workspace.

Step name:

Setup

Command:

perl ManageEnv.pl printInitialEnv
call SET_YOUR_ENVIRONMENT.bat
perl ManageEnv.pl printFinalEnv

Postprocessor:

perl ManageEnv.pl postProcess YOUR_SCRIPT_NAME.bat

Load the environment into a regular step

In this example, it is a step that obtains sources from your SCM system. The name of the first script called must match the name given to the script file in the postprocessor stage of the first step.

Step name:

Get Sources

Command:

call YOUR_SCRIPT_NAME.bat
call GET_YOUR_SOURCES.bat

Perl script - ManageEnv.pl

Below is the content of a Perl script that should be stored as "ManageEnv.pl", so it can be called from the first step, and its postprocessor.

use strict;
use File::Basename;

#-------------------------------------------------------------------------
# main Perl entry point
#
# Results:
#       Execute the specified operation
#
# Side Effects:
#       None
#
# Arguments:
#       operationName           -   operation to execute
#-------------------------------------------------------------------------

    # Make sure there is an operation name

    my $operationName = shift;
    if (! defined $operationName) {
        usage ();
    }

    # Call the named function

    for ($operationName)
    {
        /printInitialEnv/i and do       { printEnvWithHeader("INITIAL"); last; };
        /printFinalEnv/i and do         { printEnvWithHeader("FINAL"); last; };
        /postProcess/i and do           { postProcess(@ARGV); last; };

        usage ($operationName);
    }


#-------------------------------------------------------------------------
# printEnvWithHeader
#
#       Print the environment with a leading header so
#       that it can be scanned during postprocessing
#
# Results:
#      None
#
# Side Effects:
#      environment is printed to standard output
#
# Arguments:
#      label            -   A label to be added to the header to help identify the output
#-------------------------------------------------------------------------

sub printEnvWithHeader() {
    my $label = shift;
    foreach my $key (sort keys %ENV)
    {
        print "  ManageEnv $label $key=$ENV{$key}";
    }
}


#-------------------------------------------------------------------------
# postProcess
#
#       Scan standard input to find the initial and final environment variable blocks.
#       It is intended to work as ElectricCommander's postprocessor step
#
# Results:
#       None
#
# Side Effects:
#       Writes a script file to the specified output file.  The script will have commands
#       for setting those environment variables that have changed between "intitial" and "final"
#
# Arguments:
#      outFileName      -   The name of the output script file to create
#      commandTemplate  -   An optional string that specifies how to set an
#                           environment variable, using the keywords "NAME"
#                           and "VALUE".  Example:
#                               "set NAME=VALUE"
#-------------------------------------------------------------------------
sub postProcess() {
    my $outFileName = shift;
    my $commandTemplate = shift;

    my %initialEnvVars;
    my %finalEnvVars;


    # Open the output file
    if (! defined $outFileName) {
        usage();
    }
    open (OUTFILE,">$outFileName") or die "can't open output file";

    # Scan standard input, which is the stepLog file
    # Look for lines like:
    # ManageEnv FINAL envName=envValue
    # These lines are created by adding calls to this script in the step

    while (<STDIN>) {
        # Find lines with environment variables and store them
        # in the appropriate hash
        /^  ManageEnv ([KBEC-00018 - Managing environment variables across steps^ ]*) ([KBEC-00018 - Managing environment variables across steps^=]*)=(.*)$/ and do {
            my $label = $1;
            my $name = $2;
            my $value = $3;
            if ($label eq "INITIAL") {
                $initialEnvVars{$name} = $value;
            }
            elsif ($label eq "FINAL") {
                $finalEnvVars{$name} = $value;
            }
        };
    }

    # Set up default template

    if (! defined $commandTemplate) {
        # Check for the OS Type
        if ($^O =~ /MSWin/) {
            $commandTemplate = "set NAME=VALUE";
        }
        else {
            # Assume it is bash
            $commandTemplate = "export NAME='VALUE'";
            # Alternate form for "sh"
            #$commandTemplate = "setenv NAME 'VALUE'";
        }
    }

    # Compare the initial hash with the final one - record the new
    #   or changed ones

    chomp ($commandTemplate);
    foreach my $name (keys %finalEnvVars) {
        if ($initialEnvVars{$name} ne $finalEnvVars{$name}) {
            my $outline = $commandTemplate;
            $outline =~ s/VALUE/$finalEnvVars{$name}/;
            $outline =~ s/NAME/$name/;
            print OUTFILE "$outline";
        }
    }

    # Creae a placeholder postp output file

    printEmptyPostp();
}



#-------------------------------------------------------------------------
# printEmptyPostp
#
#      Creates an output file of the form expected by ElectricCommander
#      when running a postprocessor
#
# Results:
#      None.
#
# Side Effects:
#      Information is output on stdout.
#
# Arguments:
#      None.
#-------------------------------------------------------------------------

sub printEmptyPostp() {
    print("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>"
            . "<requests xmlns:xsi=\"https://www.w3.org/2001/"
            . "XMLSchema-instance\" xsi:noNamespaceSchemaLocation"
            . "=\"commander.xsd\" version=\"2.0\">"
            . "</requests>");
}


#-------------------------------------------------------------------------
# usage
#
# Results:
#       Print a usage message and die
#
# Arguments:
#       operationName           -   the name of an operation that was not
#                                   recognized
#-------------------------------------------------------------------------
sub usage () {
    # Get the name of the Perl script
    my ($name, $path, $suffix) = fileparse($0);

    my $operationName = shift;
    if (defined $operationName) {
        print "$name - unknown command $operationName";
    }
    die "Usage: perl $name printInitialEnv
       perl $name printFinalEnv
       perl $name postProcess <outputFileName> [<commandTemplate>]
       ";

}