Build a continuous integration environment with MSBuild and Jenkins
You or others have just finished writing a piece of code and submitted it to the project version repository. But what if the newly submitted code breaks down the build? What if a compilation error occurs, some tests fail, or the Code does not meet the requirements of quality standards?
The most unreliable solution is to hope that everyone is an elite and they will not make these mistakes at all. But if these problems occur, we hope that the sooner we find the better. The best way is to verify the code as long as it is submitted. This is the role of continuous integration.
There are many tools related to continuous integration. The most popular tool is Java-based Jenkins. It provides a Web interface on which users can configure jobs. Each Job contains a series of building steps. Jenkins can complete all the verification work mentioned in the first scenario. It can also perform further automated deployment or one-click deployment.
Jenkins was developed by Sun's former employee. It is based on Java, but can also be used in non-Java projects, such as PHP, Ruby on Rails, And. NET. In the. NET project, you must be familiar with another tool besides Jenkins: MSBuild.
Visual Studio uses MSBuild to build a. NET project. MSBuild only requires a script that specifies the target to be executed. Project. Csproj and. Vbproj files are all MSBuild scripts.
In this article, we will start from scratch to complete a script of our own MSBuild step by step. After it is complete, we only need one command to delete the previous build product, build a. NET application, and run the unit test. A Jenkins Job will be configured later to update the code from the code base and execute the MSBuild script. Finally, another Jenkins Job will be configured to listen to the results of the first Job. When the first step is successful, it will copy the relevant build product and put it into the web server for start-up and running.
We will use an ASP. net mvc 3 application as an example to create an ASP. net mvc 3 application in VS and select the "application" template. We also need to use a unit test project to run the test. The code can be downloaded here.
Automatically generate and publish GitBook (Nginx) using GitLab + Jenkins in the LAN)
Linux + Git + Maven + Jenkins + Neuxs automated compilation environment setup
CentOS6 install Jenkins
Use Jenkins to configure Git + Maven for automated building
Configure Jenkins + Maven + Git for continuous integration and automatic deployment
Distributed building and deployment of Jenkins-nodes
Hello, MSBuild
MSBuild is a Visual Studio build system introduced in. NET 2.0. It can execute build scripts to complete various tasks-the most important thing is to compile the. NET project into executable files or DLL. From a technical point of view, the important work of making EXE or DLL is done by the compiler (csc, vbc, etc. MSBuild calls the compiler internally and completes other necessary work (for example, copy reference-CopyLocal, and perform preparation and cleaning before and after the build)
All these tasks are completed in the MSBuild execution script. The MSBuild script is an XML file. The root element is a Project. You can use MSBuild to build your own namespace. MSBuild files must have targets. The Target is composed of tasks. MSBuild runs these tasks to complete a complete Target. Target may not contain tasks, but all targets must have names.
Next, we will create a "Hello World" MSBuild script to ensure that the configuration is correct. I suggest using VS for writing, because it can provide support for intelliisense, but it doesn't matter if you use a text editor. Because you only write an XML file, intelliisense is not very useful. First, create an XML file named "basics. msbuild". This extension is just a convention, so that we can easily recognize it as a MSBuild script. You don't have to write this extension. Add a Project element to the file as the root element and set http://schemas.microsoft.com/#/msbuild/2003to The namespace, as shown in the following figure.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"></Project>
Next, add a Target element to the Project element named "EchoGreeting"
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="EchoGreeting" /></Project>
That's all. We have an MSBuild script that can be run. Although it has nothing to do, we can use it to verify whether the current environment can run the MSBuild script. When running the script, we need to use the MSBuild executable file under the. NET Framework installation path. Open the command line and run the "MSBuild/nologo/version" command to check whether the. NET Framework installation PATH is included in the PATH environment variable. If everything is correct, you should be able to see the current version of MSBuild printed on the screen. If not, you can either put the. NET Framework installation PATH in PATH or directly use Visual Studio Command Prompt, which has already been configured.
After entering the directory where the script was saved, you can run the script by calling MSBuild with the file name as the parameter. The following execution result is displayed on my machine:
C:\>msbuild basics.msbuildMicrosoft (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 the script is executed, MSBuild first displays a startup interface and copyright information (you can hide them with the/nologo switch ). Next, a start time is displayed, and then the real build process is displayed. Because our scripts didn't do anything, the build was successful. The total time is displayed on the interface. Next let's add a Task to EchoGreeting Target to let the script do something.
<Target Name="EchoGreeting"> lt;Exec Command="echo Hello from MSBuild" /></Target>
Now, the EchoGreeting Target has an Exec Task, which executes any commands defined in the Command attribute. Run the script again to see more information. In most cases, MSBuild has a long output. You can use the/verbosity switch to only display the required information. However, MSBuild displays the text on the screen. Add another Target.
<Target Name="EchoDate"> <Exec Command="echo %25date%25" /></Target>
This Target will output the current date. The command to be run is echo % 25 date % 25, but the "%" character has a special meaning in MSBuild, so this command needs to be escaped. When an escape character is encountered, the decimal character after "%" is converted into the corresponding ASCII code. MSBuild only executes the first Target in the Project element. To execute other targets, add the/Target switch (abbreviated as/t) and the target name to MSBuild. You can also specify MSBuild to execute multiple targets, as long as the Target name is separated by a semicolon.
C:\>msbuild basics.msbuild /nologo /verbosity:minimal /t:EchoGreeting;EchoDate Hello from MSBuild Thu 08/02/2012
More practical build scripts
The demo is here first. Next we will use MSBuild to build a real project. First, download the sample code or create an ASP. NET application. Add a MSBuild script to the script, named after solution or project, with the extension ". msbuild ". Specify the MSBuild namespace as before.
Before writing a script, list the tasks that the script will do:
- Create the BuildArtifacts directory
- Build solution, put the build product (DLL, EXE, static content, etc.) under the BuildArtifacts directory.
- Run the unit test.
Because the example application is called HelloCI, the script is named HelloCI. msbuild. Add the namespace first, and then you can add the first Target, which is called Init.
<Target Name="Init"> <MakeDir Directories="BuildArtifacts" /></Target>
This Target will call MakeDir Task to create a new directory named BuildArtifacts, which is in the same directory as the script. Run the script and you will find that the directory is successfully created. If you run the Task again, MSBuild skips the Task because the directory with the same name already exists.
Next, write a Clean Target to delete the BuildArtifacts directory and the files in it.
<Target Name="Clean"> <RemoveDir Directories="BuildArtifacts" /></Target>
After understanding Init, this script should be well understood. Run the following command to delete the BuildArtifacts directory. Next we will try again in the code. In both the Init and Clean targets, we have hardcoded the directory name of BuildArtifacts into the Code. If you want to modify this name in the future, you have to change it both. Item or Property can be used to avoid this problem.
Item and Property are only slightly different. Property is composed of simple key-value pairs. You can use/property to assign values during script execution. Item is more powerful and can be used to store more complex data. We don't need any complex data here, but we need to use Items to obtain additional metadata, such as full file path.
Next, modify the script, store the path name with an Item, modify Init and Clean, and let them reference this Item.
<ItemGroup> <BuildArtifactsDir Include="BuildArtifacts\" /></ItemGroup><Target Name="Init"> <MakeDir Directories="@(BuildArtifactsDir)" /></Target><Target Name="Clean"> <RemoveDir Directories="@(BuildArtifactsDir)" /></Target>
Item is defined in ItemGroup. A Project can contain multiple ItemGroup elements to group related items. This function is particularly useful when there are many items. We define the BuildArtifactsDir element in ItemGroup and use the Include attribute to specify the BuildArtifacts directory. Remember that the BuildArtifacts directory must be followed by a slash. Finally, we use the @ (ItemName) syntax to reference this directory in the Target. To modify the directory name, you only need to change the Include attribute of BuildArtifactsDir.
There is another problem to solve. If the BuildArtifacts directory already exists, Init does nothing. That is to say, the existing files on the disk will be retained when Init is called. This is really inappropriate. If you can delete all the files in the directory and recreate them every time you call Init, this ensures that all subsequent steps are executed in a clean environment. Although we can manually call Clean every time we call Init, it is easier to add a DependsOnTargets attribute to Init Target, which will tell MSBuild, run Clean every time you execute Init.
<Target Name="Init" DependsOnTargets="Clean"> <MakeDir Directories="@(BuildArtifactsDir)" /></Target>
Now MSBuild will help us call Clean before Init. As indicated by the DependsOnTargets attribute, a Target can depend on multiple targets and can be separated by semicolons.
Next we need to compile the application and put the compiled results under the BuildArtifacts directory. Write a Compile Target to depend on Init. This Target calls another MSBuild instance to compile the application. We will upload the BuildArtifacts directory as the output directory of the compilation result.
<ItemGroup> <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>
The above script has done a few things. First, ItemGroup adds another Item called SolutionFile, which points to the solution file. Replacing hard encoding with Item or Property in the build script is a good practice.
Secondly, we created a PropertyGroup, which contains two properties: Configuration and BuildPlatform. Their values are "Release" and "Any CPU ". Of course, the Property can also be assigned a value at runtime through/property (abbreviated as/p. We also use the Condition attribute, which means that only when the two attributes have no value can we assign values to them using the data we define. This Code actually gives them a default value.
The next step is Compile Target, which depends on Init and is embedded with a MSBuild Task. It calls another MSBuild instance during running. The script defines the project to be operated by the embedded MSBuild Task. Here, we can either input another MSBuild script or the. csproj file (which is also a MSBuild script ). However, we chose to pass in the solution file of the HelloCI application. The Solution file is not a MSBuild script, but MSBuild can parse it. The script also specifies the name of the Target to be executed by the embedded MSBuild Task: "Rebuild". This Target has been imported into the. csproj file of solution. Finally, we passed three properties to the embedded Task.
OutDir
Output directory of the compilation result
Configuration
Configuration used for building (debugging, publishing, etc.)
Platform
Platforms used for compilation (x86, x64, etc)
The preceding three properties are assigned with the previously defined items and properties. OutDir Property uses the full path of the BuildArtifacts directory. The % (Item. MetaData) syntax is used here. Should this syntax look familiar? Just like the syntax for accessing C # object attributes. Any Item created by MSBuild provides some metadata for access, such as FullPath and ModifiedTime. However, this metadata is sometimes useless, because items are not necessarily files.
Configuration and Platform use the previously defined Property. The syntax format is $ (PropertyName ). Here we can see some property names retained by the system, which cannot be changed by the user. Do not use them when defining properties.
There are some things worth mentioning here. After using Property, we can use different Configuration or BuildPlatform without changing the build script, as long as the/property value is passed in during running. So "msbuild HelloCI. the msbuild/t: Compile/p: Configuration: Debug command uses the Debug Configuration to build the project, while the "msbuild HelloCI. msbuild/t: Compile/p: Configuration: Test; BuildPlatform: x86 "uses the Test Configuration on the x86 platform.
Now you can run Compile to Compile two projects under solution and put the compilation results under the BuildArtifacts directory. Before the build script is completed, only the last Target is left:
<ItemGroup> <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>
ItemGroup now has three more items: NUnitConsole points to the NUnit console runner; UnitTestDLL points to the DLL file generated by the unit test project; TestResultsPath is to be passed to NUnit, in this way, the test results will be stored in the BuildArtifacts directory.
RunUnitTests Target uses Exec Task. If a test fails, the NUnit console runner returns a non-zero result. The returned value indicates that an error has occurred in MSBuild, so the entire build state is failed.
Now this script is complete. You can use a command to delete the old build product, compile and run the unit test:
C:\HelloCI\> msbuild HelloCI.msbuild /t:RunUnitTests
We can also set a default Target for the script to save some time. Add the DefaultTargets attribute to the Project element to make RunUnitTests the default Target.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="RunUnitTests">
You can also create your own tasks. Here is an example, AsyncExec, which allows people to execute commands asynchronously. For example, a Target is used to start the Web server. If the Exec command is used, the entire build will stop until the server is closed. The AsyncExec command allows the build to continue execution without waiting for the command to end.
The complete script in this article can be downloaded here.
In the following article, I will describe how to configure Jenkins. We no longer need to manually run commands to build the entire project. Jenkins will detect the code library and automatically trigger the building once there is an update.
For more details, please continue to read the highlights on the next page: