Compartilhar via


Using C# to Automate Build Environments

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.

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

Comments

  • Anonymous
    February 01, 2008
    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.

  • Anonymous
    February 01, 2008
    The comment has been removed