Continuous Integration with MSBuild and Jenkins
You or one of your colleagues just finished writing some code and pushed the changes to the team's repository. But wait; what if the new code broke the build? What if it causes a compilation error? Or broke some tests in the test suite? Or what if the code didn't achieve the minimum value expected for some code quality metric?
One unreliable way to avoid these possible issues is to hope all members of the team are diligent enough to make sure these kinds of mistakes don't happen. But if they do happen you want to learn about them as soon as possible. The best way to do this is to have the build verified whenever any new code is pushed into the repository, which is what Continuous Integration can do for you.
There are a variety of tools available for Continuous Integration. One of the most popular is a Java-based tool called Jenkins. Jenkins allows you to create build jobs via a web interface which consists of a series of build steps. You could configure Jenkins to look for all the things mentioned in the scenario above. You could even take it further and use Jenkins to deploy your application automatically or with a single click.
Visual Studio uses MSBuild to build your .NET projects. All MSBuild requires is a build script and a target within the build script to execute. Your *.csproj and *.vbproj files are MSBuild scripts.Developed by an ex-Sun employee, Jenkins clearly shows its Java-based roots. However, it isn't just useful for Java-based projects. Jenkins can handle your Continuous Integration needs for your PHP, Ruby on Rails, or .NET projects. For .NET based projects, you're going to need to be familiar with one more tool to make use of Jenkins; MSBuild.
In this article, we'll be writing our very own MSBuild script from scratch so with a single command we'll be able to delete the previous build artifacts, build our .NET application, and run our unit tests. Then we're going to setup a Jenkins build job to pull any new code changes from our repository and run our MSBuild script. We'll also setup a second Jenkins build job to monitor the first job. Whenever our first job completes successfully, it will copy the relevant build artifacts and launch a web server with our new code.
We'll be using an ASP.NET MVC 3 application as the sample application. The application is the default application you get when you create a new ASP.NET MVC 3 application and select the "Internet application" template. We'll also be using a unit test project so we have some tests to work with. You can find the source code for the application here.
Hello, MSBuild
MSBuild is a build system for Visual Studio introduced in .NET 2.0. MSBuild runs build scripts to accomplish a variety of tasks, most notably compiling your .NET projects into executables or DLLs. Technically the compiler (csc, vbc, etc.) does the heavy lifting of producing EXEs and DLLs. MSBuild calls the compiler internally and does the rest of the steps required to produce the necessary output files (copying references marked CopyLocal, running the pre- and post-build events, etc.)
MSBuild does all this by executing a series of tasks specified in an MSBuild script. An MSBuild script is an XML file with a root Project element that has the MSBuild xml namespace specified. All MSBuild files must also contain one Target. A MSBuild Target is a collection of Tasks that MSBuild undertakes to accomplish a certain goal. A Target may contain 0 Tasks but all Targets are required to have a name.
Let's create the “Hello World” of MSBuild scripts to make sure we've got everything setup as expected. I'd suggest using Visual Studio to get some IntelliSense support, but you could use a text editor instead as the IntelliSense isn't all that useful and we are just writing XML here. Create a new XML file and name it "basics.msbuild". The msbuild extension is a convention we'll use for identifying MSBuild scripts and is not necessary. Add a Project element as the root element of your XML file and give it the XML namespace http://schemas.microsoft.com/developer/msbuild/2003 by setting the Project element's xmlns attribute like so:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"></Project>
Next, add a Target element within your Project element and name it "EchoGreeting" by setting the Target element's Name attribute:
<Target Name="EchoGreeting" />
</Project>
That's it. The above is a valid MSBuild script that should run. It won't do anything but we'll use it to verify we can run MSBuild scripts. To run the above build script, we'll use the MSBuild executable located within the .NET framework installation folder. Open up the command prompt and verify you have the .NET framework installation folder in your path by executing "MSBuild /nologo /version". You should see the version number of your MSBuild printed on the screen. If not, add the .NET framework installation folder to your path or simply use the Visual Studio Command Prompt which already has everything set up.
Navigate to the directory you've saved the above build script in and execute the script by calling the MSBuild executable and passing in the file name of the build script as an argument. Here's what executing the above script looks like on my local machine:
C:\> msbuild basics.msbuild
Microsoft (R) Build Engine Version 4.0.30319.1
[Microsoft .NET Framework, Version 4.0.30319.269]
Copyright (C) Microsoft Corporation 2007. All rights reserved.
Build started 8/2/2012 5:59:45 AM.
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.03
After executing the build script, MSBuild first displays a startup banner and copyright message (which can be suppressed with the /nologo switch). Next the build’s startup time is printed and the build is run at this point. Since our build script doesn't do anything, nothing else is shown, the build is considered a success, and the time elapsed is printed. Now let's add a Task to our EchoGreeting Target so our build actually does something.
<Exec Command="echo Hello from MSBuild" />
</Target>
The above modified EchoGreeting Target now contains an Exec Task. An Exec Task runs the command specified in the Command attribute. Try running the build script again. You ought to see more text appear on the screen. For the most part, MSBuild is being verbose with its output. You could edit it down by setting the /verbosity switch to minimal. In any case, you can see that MSBuild has successfully echoed our text. Before moving on, let's add another Target to the Project element.
<Exec Command="echo %25date%25" />
</Target>
The above target echoes the date onto the screen. The command to do so is actually "echo %date%" but the "%" character has special meaning in MSBuild so has to be escaped. Escaping characters requires substituting them for the ASCII code for the character in hexadecimal with a preceding "%" character. MSBuild will only run the first Target specified in the Project element. To run a different Target instead, you'll have to pass in the Target name via the "/target" switch (or "/t" for short). You can also instruct MSBuild to execute multiple Targets by separating the Target names with semicolons.
C:\>msbuild basics.msbuild /nologo /verbosity:minimal /t:EchoGreeting;EchoDate Hello from MSBuild Thu 08/02/2012
A More Useful Build Script
That's enough of the test build script. Let's use MSBuild to build an actual project. Grab a copy of the sample application source code or create your own ASP.NET application to work with. Add an MSBuild script and name it exactly the same name of your solution or project but with the ".msbuild" extension, then add the Project element with the MSBuild namespace on the Project element as before.
Before we start with this build script, let's list the things we want this script to do:
1. Create a BuildArtifacts directory
2. Build the solution and place the build artifacts (DLLs, EXEs, content, etc.) inside the BuildArtifacts directory
3. Run the unit tests suite
2. Build the solution and place the build artifacts (DLLs, EXEs, content, etc.) inside the BuildArtifacts directory
3. Run the unit tests suite
Since the sample application is called HelloCI, we're going to call our build script HelloCI.msbuild. Add the Project element with the MSBuild xmlnamespace as before. Now let's add our first Target: Init.
<MakeDir Directories="BuildArtifacts" />
</Target>
The above Target makes use of the MakeDir Task to create a new directory called BuildArtifacts at the same location as the build script. Try running the build script. You should find the directory was successfully created in the same directory as the build script. If you run the build script again, MSBuild will simply skip the MakeDir Task since the expected directory already exists.
Next let's add a Clean Target. The Clean Target will remove the BuildArtifacts directory and any files within it.
<RemoveDir Directories="BuildArtifacts" />
</Target>
The above should be pretty self-explanatory if you've understood the Init Target. Try running the Clean Target. The BuildArtifacts directory should be removed. Next, let's get rid of a bit of duplication. In both the Init and Clean Targets we've hardcoded the name of the BuildArtifacts directory. If we wanted to rename the directory in the future, we'd need to do so in two places. We can make use of MSBuild Items or Properties to avoid such issues.
Items and Properties in MSBuild are very similar but have one subtle difference. Properties are simply key-value pairs and can be set when running the build script by using the /property switch. Items are more powerful than Properties since they allow complex values to be stored within them. We won't be using any complex values but we'll need to use Items to get some extra metadata from some Items like the full path of a file.
We'll use an Item to store the name of the directory we'll be working with and then modify the Init and Clean Targets to reference the Item.
<BuildArtifactsDir Include="BuildArtifacts\" />
</ItemGroup>
<Target Name="Init">
<MakeDir Directories="@(BuildArtifactsDir)" />
</Target>
<Target Name="Clean">
<RemoveDir Directories="@(BuildArtifactsDir)" />
</Target>
Items are defined within an ItemGroup element. There can be multiple ItemGroup elements within a Project element allowing you to group related items together. This becomes useful when you've got a build script with a lot of Items. Within the ItemGroup element is our BuildArtifactsDir element and we include the BuildArtifacts directory using the Include attribute. Don't forget the trailing slash when specifying the BuildArtifacts directory. Finally, we reference the BuildArtifacts directory in our Targets using the @(ItemName) syntax. Now, if we wanted to change the name of the directory, we would simply change the value of Include attribute in the BuildArtifacts Item.
We should resolve one more issue before moving on. Currently, if the BuildArtifacts directory exists, the Init Target does nothing. This means any existing files are left on the disk when calling the Init Target. It would be better to have the BuildArtifacts and all existing files within it removed and have the directory recreated whenever the Init Target is called. We could manually call the Clean Target before calling the Init Target every time, but a simpler solution is to add a DependsOnTargets attribute to the Init Target telling MSBuild to run the Clean Target every time the Init Target is executed.
<MakeDir Directories="@(BuildArtifactsDir)" />
</Target>
Now we don't have to call Clean before calling Init; every time we call Init, MSBuild will first run Clean. As the attribute name implies, we can have multiple Targets run before a Target. When specifying multiple Targets, separate them by semi-colons.
The stage is now set to compile our application and place the compilation output into our BuildArtifacts directory. We'll be creating a Compile Target, which depends on the Init Target. The Compile Target will call another instance of MSBuild to compile the application. We'll also be passing the BuildArtifacts directory as the directory our compilation output should be placed.
<BuildArtifactsDir Include="BuildArtifacts\" />
<SolutionFile Include="HelloCI.sln" />
</ItemGroup>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>
<BuildPlatform Condition=" '$(BuildPlatform)' == '' ">Any CPU</BuildPlatform>
</PropertyGroup>
<Target Name="Compile" DependsOnTargets="Init">
<MSBuild Projects="@(SolutionFile)" Targets="Rebuild"
Properties="OutDir=%(BuildArtifactsDir.FullPath);Configuration=$(Configuration);Platform=$(BuildPlatform)" />
</Target>
There are quite a few things happening here. First, we've added another Item to the ItemGroup called "SolutionFile" that references our solution file. It's good practice to not hardcode values within the build script; use an Item or Property instead.
Next, we created a PropertyGroup and added two Properties within it; Configuration and BuildPlatform. We've set the values to these properties to "Release" and "Any CPU" respectively. However, Properties can be set when executing an MSBuild script by using the /property switch (or /p for short). We've used the Condition attribute on both Properties to tell MSBuild to only set the Properties as the values we've defined if they have not been set already. What we've done is set a default value for both Properties.
We then defined a new Target named Compile which depends on the Init Target. Our Compile Target has within it an MSBuild Task. By doing so, we've called another instance of MSBuild from our MSBuild script. We've specified the Project we want the inner MSBuild Task to execute. Here we could specify another MSBuild script (which are also known as projects), or we could pass in a .csproj file which is also an MSBuild script. Instead, we've passed in the solution file for our HelloCI application. Solution files are not MSBuild scripts but MSBuild is capable of parsing solution files. We've also specified the Target the inner MSBuild Task to execute to be the "Rebuild" Target, which has been imported into the .csproj files within our solution. Finally, we set three Properties for our inner MSBuild Task:
OutDir
The output directory the inner MSBuild Task should save the compilation output.
Configuration
The Configuration to use when building (Debug, Release, etc.)
Platform
The Platform to compile for (x86, x64, etc.)
The output directory the inner MSBuild Task should save the compilation output.
Configuration
The Configuration to use when building (Debug, Release, etc.)
Platform
The Platform to compile for (x86, x64, etc.)
When setting each of the above Properties, we've used the Items and Properties we've defined earlier. For the OutDir Property, we passed in the full path of the BuildArtifacts directory. To do so, we've used the %(Item.MetaData) syntax. The syntax should seem familiar and look like how you would access a property on an object in C#. MSBuild gives you access to certain MetaData for any Item you create like the FullPath or ModifiedTime. This MetaData is not always useful since you could have Items which are not files.
As for the Configuration and Platform Properties, we've reference the Configuration and BuildPlatform properties we've defined by using the $(PropertyName) syntax. Here's a list of reserved Property names which are off-limits. Try not use any of them when defining your own Properties.
One more thing to note is that the use of Properties allows us to build using a different Configuration or BuildPlatform Properties without changing our build script; we just pass in different values when running MSBuild by using the /property switch. So running "msbuild HelloCI.msbuild /t:Compile /p:Configuration:Debug" will build the projects in the Debug Configuration and running "msbuild HelloCI.msbuild /t:Compile /p:Configuration:Test;BuildPlatform:x86" will build the projects in the Test and x86 Configuration.
Running the Compile Target now should compile both projects in the solution and place the output in the BuildArtifacts directory. We now have one final Target to add before wrapping up our build script.
<BuildArtifacts Include="BuildArtifacts\" />
<SolutionFile Include="HelloCI.sln" />
<NUnitConsole Include="C:\Program Files (x86)\NUnit 2.6\bin\nunit-console.exe" />
<UnitTestsDLL Include="BuildArtifacts\HelloCI.Web.UnitTests.dll" />
<TestResultsPath Include="BuildArtifacts\TestResults.xml" />
</ItemGroup>
<Target Name="RunUnitTests" DependsOnTargets="Compile">
<Exec Command='"@(NUnitConsole)" @(UnitTestsDLL) /xml=@(TestResultsPath)' />
</Target>
In the above, we've added 3 new Items to our ItemGroup; NUnitConsole, which points to our NUnit console runner that will run our unit tests project; UnitTestsDLL, which points to the unit tests DLL the unit tests project produces; and TestResultsPath, which we'll pass to NUnit so it will place the test results in our BuildArtifacts directory.
The RunUnitTests Target makes use of the Exec Task which will run the command specified in the Command attribute. If one test fails in our test suite, the NUnit console runner will return a non-zero return value. The non-zero return value will signal to MSBuild that something went wrong and will mark the build as failed.
We now have a robust build script capable of removing old artifacts, compiling our projects, and running our unit tests in one command.
C:\HelloCI\> msbuild HelloCI.msbuild /t:RunUnitTests
We could also set a default Target for our build script so we wouldn't have to specify a Target. Just add a DefaultTargets attribute to the root Project element specifying the RunUnitTests Target as the default Target.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="RunUnitTests">
You can even add your own custom Tasks to your MSBuild scripts. As an example, check out theAsyncExec project. It allows you to execute commands asynchronously. For instance, if you added a Target that starts a web server, using the Exec command would cause the build to wait unit the web server shuts down before it reported the build complete. Using the AsyncExec command will allow you to run commands without causing the build to wait for them to complete.
You’ll find the completed build script in the sample application repository.
In my next article, I will discuss setting up Jenkins, so instead of having to run 1 command manually to build our projects, we can have Jenkins monitor our source code repository and start a build whenever we push new code to the repository.
This is the second part of a two-part article on Continuous Integration. If you haven't already, please check out Part 1 of Continuous Integration with MSBuild and Jenkins first.
Meet Jenkins
Jenkins was created by Kohsuke Kawaguchi in 2004 and was originally known as Hudson. Sun, Kawaguchi's employer at the time, was providing support for Hudson by 2008. After Oracle's purchase of Sun in 2009, disagreements arose between Oracle and Hudson's developer community. In 2011, due to Oracle's claims to the Hudson trademark, the Hudson developer community renamed the project Jenkins, and continued their work on the project without Oracle's interference.
At first glance, Jenkins seems like a Continuous Integration tool for Java-based projects. There are jobs designed for Maven projects, there are default plugins intended for Java-based project, not to mention that plugins are written in Java.
Setting Up JenkinsHowever, Jenkins is a remarkably flexible tool to build practically any kind of project by combining any source-code management system with any build system. We will take advantage of this flexibility and use Jenkins to pull our changes from a Mercurial repository to build our project using MSBuild. To begin, we'll need to download and install Jenkins and install the Mercurial and MSBuild plugins to get Mercurial and MSBuild support within Jenkins.
Grab the installer for Jenkins at the official website. The Windows installer is extremely simple and will install Jenkins as a Windows service. By default, you'll access Jenkins at http://localhost:8080 , so make sure nothing else is running at that port when you first start Jenkins.
Once you startup Jenkins, you'll want to install the necessary plugins. Click the "Manage Jenkins" link and then the "Manage Plugins" link. Click the "Available" tab to view the plugins available to install. You'll need a working internet connection to view the contents of this page. Use the filter to find the Mercurial and MSBuild plugins and install them by clicking their checkboxes. Confirm the plugins were successfully installed by navigating to the "Installed" tab. You may see a message telling you Jenkins needs to restart to complete the plugin installation. Allow Jenkins to restart and navigate to the installed plugins list to confirm that the Mercurial and MSBuild plugins were successfully installed.
We'll need to do some further configuration for Mercurial so Jenkins will know where to find your Mercurial installation. Head back to the "Manage Jenkins" page and click the "Configure System" link. Look for the "Mercurial" section. If you don't find the Mercurial section then the Mercurial plugin was not properly installed. Click the "Add Mercurial" button. Within this section, you'll need to define the name, installation directory, and executable of this Mercurial installation. Give the installation whatever name is convenient for you. As for the Installation directory, give it the directory where the Mercurial executable (hg.exe) is located. Finally, for the executable, type in "INSTALLATION/hg.exe". The word installation will be substituted for the installation directory you've provided above.
Creating a Jenkins Job
Next navigate back to the Dashboard by clicking the "Back to Dashboard" link and click the "New Job" link. Here you'll be presented with a list of job types, select the "Build a free-style software project," give your job the name "HelloCI-RunUnitTests," and click OK.
You'll now be at the job configuration page. There are quite a few options available here and they generally have helpful descriptions available when you click the small help icons to their right. We'll be configuring two settings for now; we'll let Jenkins know where to locate our source code repository and how to build our project using MSBuild.
Find the "Source Code Management" section and select Mercurial. Type in the name of Mercurial installation you defined in the "Configure System" page in the "Mercurial Version" textbox. Then type in the URL of your Mercurial repository in the "Repository URL" textbox. The reposotory URL can be a file system path as well. Finally, pass in the branch you'd like tracked in the "Branch" textbox.
Next, scroll down to the "Build" section and click the "Add build step" button. You should be presented with a list of build step types including "Build a Visual Studio project or solution using MSBuild". If you don't see this option, the MSBuild plugin wasn't properly installed.
After clicking the link, type in the name of the build script in the "MSBuild Build File" textbox (HelloCI.msbuild). We want Jenkins to execute our "RunUnitTests" Target so if you haven't specified that "RunUnitTests" Target in the DefaultTargets attribute, you can pass in the /target switch by typing it in "Command Line Arguments" textbox. Just type in "/t:RunUnitTests" and that should do it (/t is short for /target).
Save your build job by clicking the "Save" or "Apply" buttons at the botton. This build job should pull and compile our project and run the unit test suite whenever it's triggered. Let's trigger a build manually to confirm everything is set up correctly. Head back to the dashboard and you should see your build job in the center of the screen. Click the name of the build job to be taken to the job's page. On the list of links to the left, click the "Build Now" link to trigger a build. Jenkins will then schedule a build job and will begin to execute the job. Underneath the list of links should be "Build History" list which will show a summary of all builds for this job. You should see your first build job begin to execute and a progress bar begin to fill. Also, a small circular icon representing the status of the build should be visible. If it's blinking, then the build is in progress. When the icon stops blinking, it will be red or blue. If it's red, the build failed while blueindicates a successful build.
If your Jenkins job was able to access your Mercurial repository, find your HelloCI.msbuild build script in your repository and execute the "RunUnitTests" Target successfully to completion; the icon should turn blue. You have successfully completed your first Jenkins build. If the build failed, click the timestamp of the build in the "Build History" list and click the "Console Output" link to view every command Jenkins executed and the resultant output. Use the console output to figure out what went wrong.
Triggering Builds
After your build succeeds, the next step is to get Jenkins to automatically start a build whenever you push new code into your repository. There are a few ways to accomplish this. You could have Jenkins periodically start a build. This is wasteful if there was no new code pushed in the specified time period. Another option would be to have Jenkins poll the Mercurial repository occasionally to check if there was any new code pushed in. The downside to this approach is when you push new code in you'll need to wait until the poll period elapses to have the new build started. You could have Jenkins poll every minute so the wait wouldn't be very long, but there is a more elegant solution. Add a post-commit hook to the Mercurial repository so whenever the repository receives new code, it informs Jenkins of the fact and Jenkins in turn starts a build immediately.
This approach requires you to add the hook within your repository settings file located within the .hg folder (if it doesn't exist, just add a text file named .hgrc). Within it, you'll need to get Mercurial to visit the following URL http://localhost:8080/job/JOBNAME/build?delay=0sec (where JOBNAME is replaced with the name of your build job) so Jenkins is notified to start a new build. Unfortunately, on Windows there is no command like Linux's wget, which can make HTTP requests. I've personally used a Ruby one-liner to accomplish this goal. You can also use a Powershell script to create an instance of System.Net.WebClient and call the DownloadString method on the above mentioned URL. I'll leave the implementation of this approach as an exercise to the reader.
If you'd rather not use this approach, you can always use the polling technique. To do this, return to the build configuration page. Under the "Build Triggers" section, select "Poll SCM" and type in how often you'd like to poll the repository in the "Schedule" textbox. You'll need to type in the schedule using cron-style syntax. To poll the repository every minute, type in "1 * * * *". Use the help icon to the right of the "Poll SCM" text to guide you in setting up your schedule.
Build Pipelines
You can have Jenkins activate jobs based on successful completion of other builds. You can create build pipelines, where a successful completion of one job can trigger other job. Jobs that activate other jobs in the pipeline are upstream to the jobs they activate and jobs that are activated by other jobs are downstream to the jobs that activate them.
Build pipelines allow for all sorts of scenarios; having more time-consuming tests executed when the unit test suite passes, running some static code analysis tools, or deploying the build to staging or production environments. To demonstrate this capability, we'll use Jenkins to automatically start up our application on a web server whenever it successfully builds.
To accomplish this goal, we need to do only three things; trigger this job whenever our HelloCI-RunUnitTests job successfully builds, copy the build artifacts from the HelloCI-RunUnitTests job and finally start the web server. Before you begin, you need to install the Copy Artifacts plugin. Head back to the Manage Plugins page and follow the same routine as the earlier plugins to do so. Don't forget to restart Jenkins when prompted.
Before creating a new job, we need to tell the HelloCI-RunUnitTests job to save certain build artifacts our new job will need. Go back to the job configuration page of the HelloCI-RunUnitTests job and add an "Archive the artifacts" post-build action under the "Post-build Action" section (below the "Build" section). You'll find a textbox labeled "Files to archive". You want to archive everything within the BuildArtifacts folder your build script created as well as the packages folder so you have the access to the NuGet packages you may need to run your application. To specify that you want a particular folder and its contents, you'll need to type in the folder name followed by a forward-slash and two asterisks. Separate different files and folders you may need by commas. In our case, to get the BuildArtifacts and packages folders, type in "BuildArtifacts/**,packages/**".
Next, create a new Jenkins job and name it "HelloCI-StartWebServer". In the job configuration page, select "Build after other projects are built" in the "Build Triggers" section and specify the earlier job as the project to trigger this job.
You now need to add a build step so the first thing this job will do is copy the save artifacts from the earlier job. Do this by using the "Copy artifacts from another project" build step (which should be available after installing the Copy Artifacts plugin). All you need to specify is the earlier job as the project whose artifacts will be copied.
Finally, add a build step to start the web server. To do this, add an "Execute Windows batch command" build step. We'll use Visual Studio's web server, Cassini, to run our application. Cassini can be found at "C:\Program Files (x86)\Common Files\microsoft shared\DevServer\10.0\WebDev.WebServer40.EXE". Running Cassini is pretty simple, just pass it the path where the web application is rooted using the path parameter and an optional port number if you don't want to, or can't, use the default port 80.
The "Execute Windows batch command" build step requires a command that will execute at same directory the BuildArtifacts and packages directories were copied to. Add the following command into the textbox labeled "Command":
"C:\Program Files (x86)\Common Files\microsoft shared\DevServer\10.0\WebDev.WebServer40. EXE" /port:9876 /path BuildArtifacts/_PublishedWebsites/HelloCI.Web/.
The above command will start Cassini, point it at the application that has been copied from the earlier project, and run the server on port 9876.
All the configuration should now be ready. Try making a change to your application and committing your changes to the repository the HelloCI-RunUnitTests job is tracking. Jenkins should notice the new code, run the HelloCI-RunUnitTests job. If the code compiles and the unit test suite completes successfully, Jenkins should then run the HelloCI-StartWebServer job that copies the artifacts from the HelloCI-RunUnitTests job and start Cassini on port 9876. If it all worked out, you've successfully setup your automated Continuous Integration system.
Final Thoughts
There's so much more to cover about MSBuild and Jenkins. You can set them up to upgrade from Continuous Integration to Continuous Deployment. All you need is a build job capable of deploying your build artifacts to your deployment target. For ASP.NET applications, this could be as simple as a Jenkins job copying the build artifacts to a certain directory. You can then have that Jenkins job activated whenever a build passes all the tests suites so new features are automatically released to production by Jenkins.
If you're not ready to take it that far, you go forward by using the Promoted Build plugin to promote a build for release. Promoting a build simply means marking a build as promoted when it passes a certain criteria. The criteria for promotion could be when a build is manually approved (when a button is clicked) by one or more individuals. With the Promoted Build plugin, changes could be automatically released onto the development server so all new features are centrally located. A team lead could then promote a particular development build to a staging server. The QA team or other project stakeholders can then sign off (by promoting the staging server build) and the staging server build can be automatically deployed to production by Jenkins.
John Ferguson Smart has authored an excellent book on Jenkins entitled "Jenkins: The Definitive Guide". Besides a thorough briefing on Jenkins' features, the book also gives a great tutorial on how to setup Jenkins to create the build pipeline described above. The book also explains how to secure your Jenkins installation, monitor its resource usage (e.g. disk space in use), distribute your builds to slave machines, and even setup a notification system so you'll be informed whenever a build starts, succeeds, or fails. The book, published by O'Reilly, is available for free, and is a must-have if you're just getting started with Jenkins.
Having an automated build and deployment system is just as important as a strong testing culture and the use of version control in professional software shops of today. I hope that you've gained enough familiarity on how to set up a Continuous Integration system that you can integrate in your own systems.
You may worry about the time to set everything up; don’t be. Set everything up piece-by-piece as you find the time. Eventually, you'll have it all set up and then you can begin to enjoy the fruit of your labor. You will notice bugs earlier as Jenkins displays a broken build, you'll have a systematic deployment process and won't have to remember all the steps it takes to successfully deploy your system. Most importantly, you'll have more time to do useful things like build value-adding features to your application, begin working on new projects, or perhaps being able to go home without having to worry about an upcoming release.
About the Author
Mustafa Saeed Haji Ali lives in Hargeisa, Somaliland. He is a software developer that usually works with ASP.NET MVC. Mustafa enjoys testing and using JavaScript frameworks like KnockoutJS, AngularJS and SignalR and has a passion for evangelizing best practices.
No comments:
Post a Comment