Using C# to Automate Build Environments

Using C# to Automate Build Environments

  • Comments 2

Recently, I was tasked with writing a build automation system for our test group, and while the build system is pretty robust at this point, I've run into quite a few challenges in getting the most critical part of it to work: The part that runs our build scripts. I thought I'd share some of the tips and tricks I've picked up along the way.

The first thing I had to figure out was how to get each of the processes I needed to run to run in the correct environment. Like most build environments, ours involves setting a whole bunch of environment variables to direct the scripts to the right places, set the binary output folder, etc. But environments are per-process, and getting a set environment to propagate from one process to another is more difficult than it sounds when you're running those processes from one that is essentially running outside that environment. Child processes always inherit the environment that the parent process is running in.

Unfortunately, the automation system is set up as a Windows service, which necessarily has to run in a default environment. Additionally, the environments change depending on the architecture of the requested build, so starting the service in a specific build environment just won't work.

The solution? Since all information about a build "job" is stored in a central database, I reorganized my code so that the service can launch a process that sets up the proper build environment and then calls a separate program to execute the build scripts for that job. The "job processor", as I call it, reads the database and executes the specified tasks within the build environment, since it's inherited that environment from the process launched by the backend service. And the service waits for that master process to exit before going on to process the next job, so it works well.

Here's a code sample of the backend service launching that master process:


[code]

Process oProc = new Process();

oProc.StartInfo.FileName = "cmd.exe";

oProc.StartInfo.WorkingDirectory = "[buildenvironment_folder]";

oProc.StartInfo.RedirectStandardInput = true;

oProc.StartInfo.UseShellExecute = false;


oProc.Start();

oProc.StandardInput.WriteLine("[setup_buildenvironment.cmd] [params]");

oProc.StandardInput.WriteLine("processjob.exe [jobnum] [params]");

oProc.StandardInput.WriteLine("exit");


oProc.WaitForExit();

oProc.Close();

// Post-job code here.

[/code]


Basically, this code starts a CMD window and then, just like a regular user, sends the necessary commands to set everything up and run the job processor, then exit once the job processor is finished. The job processor reports back to the backend service by way of the database, by setting flags as appropriate as to whether certain tasks succeeded or failed.

So far, it seems to be a pretty robust solution to a very non-intuitive problem. But the challenges didn't end here - in my next article, I'll describe some of the interesting things I learned while writing the job processor. 

- Matt

  • I'm not sure how robust that really is, since the compexity of error detection and reporting isn't really considered.  For example, if the build errors out, there's no real good way to detect that.

    Wouldn't it be better to directly invoke the build process executable(s), so that you could at a minimum check the process exit code?  Nearly all build tools signal errors in this way.

    In addition, invoking the executable directly makes it easier to associated standard output and standard error output with each individual executable.  That probably simplifies the process of parsing the tool output, if that is required.

  • Good points.  But there are two reasons for doing it this way.  The first, as I mentioned in the post, is that the build tools require a specific, predefined environment in order to operate properly, and it isn't possible (or feasible) to launch the build processes directly and get them into the correct environment without having the service process already running in that environment.  Since the environment changes depending on which architecture I'm building, I can't rely on that system.

    The other reason is that this method enables the backend service to be very simple, and thus very stable.  It has just three jobs: Monitor the database for new build jobs, launch a job processor to execute those jobs, and send out build notification emails when the jobs are complete.  By relegating the actual job processing to another program, any instability that occurs there doesn't risk bringing the whole service down.

    The job processor works much more like how you described - it redirects the standard output and error streams to log files, and it watches those streams for error messages where necessary.  Watch for my next posting on the job processor - I'll go into more detail about this then. :)

    -- Matt

Page 1 of 1 (2 items)
Leave a Comment
  • Please add 8 and 7 and type the answer here:
  • Post