阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

用MSBuild和Jenkins搭建持续集成环境

248次阅读
没有评论

共计 23322 个字符,预计需要花费 59 分钟才能阅读完成。

你或其他人刚刚写完了一段代码,提交到项目的版本仓库里面。但等一下,如果新提交的代码把构建搞坏了怎么办?万一出现编译错误,或者有的测试失败了,或者代码不符合质量标准所要求的底限,你该怎么办?

最不靠谱的解决方案就是寄希望于所有人都是精英,他们根本不会犯这些错误。但如果真的出现了这些问题,我们就希望发现的越早越好。最好的方式就是只要有代码提交,我们就有某种方式对它进行验证。这就是持续集成的作用。

持续集成相关的工具有很多。最流行的要数一款基于 Java 的名叫 Jenkins 的工具。它提供了 Web 界面,用户可以在界面上配置 Job,每个 Job 都包含一系列的构建步骤。Jenkins 可以完成开头那个场景中所提到的所有验证工作,它还能更进一步做自动化部署或者一键式部署。

Jenkins 是由 Sun 的前员工开发的,它的根基是 Java,但也可以用在非 Java 的项目里,比如 PHP、Ruby on Rails、.NET。在.NET 项目里,你除了 Jenkins 之外还要熟悉另一样工具:MSBuild。

Visual Studio 用 MSBuild 构建.NET 项目。MSBuild 所需的仅仅是一个脚本,在脚本中指定要执行的 target。项目中的 .csproj 和 .vbproj 文件都是 MSBuild 脚本。

在这篇文章中,我们会从头开始,一步步完成一个属于我们自己的 MSBuild 脚本。在它完成以后,我们只需要一个命令就可以删除之前的构建产物,构建.NET 应用,运行单元测试。后面我们还会配一个 Jenkins Job,让它从代码库中更新代码,执行 MSBuild 脚本。最后还会配另一个 Jenkins Job,让它监听第一个 Job 的结果,当第一步成功以后,它会把相关的构建产物复制出来,放到 web 服务器里启动运行。

我们用一个 ASP.NET MVC 3 应用做例子,在 VS 里面创建 ASP.NET MVC 3 应用并选择“application”模版就行。我们还要用一个单元测试项目来跑测试。代码可以在这里下载。

局域网内利用 GitLab+Jenkins 自动生成 GitBook 并发布 (Nginx)  http://www.linuxidc.com/Linux/2016-05/131136.htm

Linux+Git+Maven+Jenkins+Neuxs 自动化编译环境搭建 http://www.linuxidc.com/Linux/2016-02/128652.htm

CentOS6 安装 Jenkins  http://www.linuxidc.com/Linux/2016-05/131365.htm

使用 Jenkins 配置 Git+Maven 的自动化构建 http://www.linuxidc.com/Linux/2016-02/128641.htm

Jenkins+Maven+Git 搭建持续集成和自动化部署的配置手记 http://www.linuxidc.com/Linux/2015-06/118606.htm

Jenkins 的分布式构建及部署——节点  http://www.linuxidc.com/Linux/2015-05/116903.htm

你好,MSBuild

MSBuild 是在.NET 2.0 中引入的针对 Visual Studio 的构建系统。它可以执行构建脚本,完成各种 Task──最主要的是把.NET 项目编译成可执行文件或者 DLL。从技术角度来说,制作 EXE 或者 DLL 的重要工作是由编译器(csc,vbc 等等)完成的。MSBuild 会从内部调用编译器,并完成其他必要的工作(例如拷贝引用──CopyLocal,执行构建前后的准备及清理工作等)

这些工作都是 MSBuild 执行脚本中的 Task 完成的。MSBuild 脚本就是 XML 文件,根元素是 Project,使用 MSBuild 自己的命名空间。MSBuild 文件都要有 Target。Target 由 Task 组成,MSBuild 运行这些 Task,完成一个完整的目标。Target 中可以不包含 Task,但是所有的 Target 都要有名字。

下面来一起创建一个“Hello World”的 MSBuild 脚本,先保证配置正确。我建议用 VS 来写,因为它可以提供 IntelliSense 支持,不过用文本编辑器也无所谓,因为只是写个 XML 文件,IntelliSense 的用处也不是很大。先创建一个 XML 文件,命名为“basics.msbuild”,这个扩展名只是个约定而已,好让我们容易认出这是个 MSBuild 脚本,你倒不用非写这样的扩展名。给文件添加一个 Project 元素作为根元素,把 http://schemas.microsoft.com/developer/msbuild/2003 设置成命名空间,如下所示

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
</Project>

下一步,给 Project 元素添加一个 Target 元素,起名叫“EchoGreeting”

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="EchoGreeting" />
</Project>

这就行了。我们已经有了一个可以运行的 MSBuild 脚本。它虽然还啥事都没干,但我们可以用它来验证当前环境是不是可以运行 MSBuild 脚本。在运行脚本的时候,我们要用到.NET 框架安装路径下的 MSBuild 可执行文件。打开命令行,执行“MSBuild /nologo /version”命令,看看.NET 框架安装路径是不是放到了 PATH 环境变量里面。如果一切正确,你应该能看到屏幕上打印出 MSBuild 的当前版本。倘若没有的话,或者把.NET 框架安装路径放到 PATH 里面去,或者直接用 Visual Studio Command Prompt,它已经把该配的都配好了。

进入存放刚才那个脚本的目录后,以文件名当作参数调用 MSBuild,就可以执行脚本了。在我的机器上可以看到下面的执行结果:

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

执行完脚本以后,MSBuild 会首先显示一个启动界面和版权信息(用 /nologo 开关可以隐藏掉它们)。接下来会显示一个启动时间,然后便是真正的构建过程。因为咱们的脚本啥都没干,所以构建就直接成功了。总计用时也会显示在界面上。下面咱们来给 EchoGreeting Target 添加一个 Task,让脚本真的干点事。

<Target Name="EchoGreeting">
    lt;Exec Command="echo Hello from MSBuild" />
</Target>

现在 EchoGreeting Target 有了一个 Exec Task,它会执行 Command 属性中定义的任何命令。再运行一次脚本,你应该能看到更多信息了。在大多数时候,MSBuild 的输出信息都很长,你可以用 /verbosity 开关来只显示必要信息。不过无论怎样,MSBuild 都会把我们的文字显示到屏幕上。下面再添加一个 Target。

<Target Name="EchoDate">
 <Exec Command="echo %25date%25" />
</Target>

这个 Target 会输出当前日期。它的命令要做的事情就是“echo %25date%25”,但是“%”字符在 MSBuild 中有特殊含义,所以这个命令需要被转义。当遇到转义字符的时候,“%”后面的十进制字符会被转成对应的 ASCII 码。MSBuild 只会执行 Project 元素中的第一个 Target。要执行其他 Target 的时候,需要把 /target 开关(可简写为 /t)加上 Target 名称传给 MSBuild。你也可以指定 MSBuild 执行多个 Target,只要用分号分割 Target 名字就可以。

C:\>msbuild basics.msbuild /nologo /verbosity:minimal /t:EchoGreeting;EchoDate
 Hello from MSBuild
 Thu 08/02/2012

更实用的构建脚本

演示就先到这里。下面来用 MSBuild 来构建一个真实项目。首先把示例代码下载下来,或是自己创建一个 ASP.NET 应用。给它添加一个 MSBuild 脚本,以 solution 或 project 名字给脚本命名,扩展名用“.msbuild”。照先前一样指定 MSBuild 命名空间。

开始写脚本之前,先把脚本要干的事情列出来:

  1. 创建 BuildArtifacts 目录
  2. 构建 solution,把构建产物(DLL,EXE,静态内容等等)放到 BuildArtifacts 目录下。
  3. 运行单元测试。

因为示例应用叫做 HelloCI,于是这个脚本也就命名为 HelloCI.msbuild。先添加命名空间,然后就可以添加第一个 Target 了,我管它叫做 Init。

<Target Name="Init">
 <MakeDir Directories="BuildArtifacts" />
</Target>

这个 Target 会调用 MakeDir Task 创建一个新的目录,名叫 BuildArtifacts,跟脚本在同一目录下。运行脚本,你会发现该目录被成功创建。如果再次运行,MSBuild 就会跳过这个 Task,因为同名目录已经存在了。

接下来写一个 Clean Target,它负责删除 BuildArtifacts 目录和里面的文件。

<Target Name="Clean">
 <RemoveDir Directories="BuildArtifacts" />
</Target>

理解了 Init 之后,这段脚本就应该很好懂了。试着执行一下,BuildArtifacts 目录应该就被删掉了。下面再来把代码中的重复干掉。在 Init 和 Clean 两个 Target 里面,我们都把 BuildArtifacts 的目录名硬编码到代码里面了,如果未来要修改这个名字的话,就得同时改两个地方。这里可以利用 Item 或 Property 避免这种问题。

Item 和 Property 只有些许差别。Property 由简单的键值对构成,在脚本执行的时候还可以用 /property 赋值。Item 更强大一些,它可以用来存储更复杂的数据。我们这里不用任何复杂数据,但需要用 Items 获取额外的元信息,例如文件全路径。

接下来修改一下脚本,用一个 Item 存放路径名,然后修改 Init 和 Clean,让它们引用这个 Item。

<ItemGroup>
 <BuildArtifactsDir Include="BuildArtifacts\" />
</ItemGroup>

<Target Name="Init">
 <MakeDir Directories="@(BuildArtifactsDir)" />
</Target>

<Target Name="Clean">
 <RemoveDir Directories="@(BuildArtifactsDir)" />
</Target>

Item 是在 ItemGroup 里面定义的。在一个 Project 中可以有多个 ItemGroup 元素,用来把有关系的 Item 分组。这个功能在 Item 较多的时候特别有用。我们在 ItemGroup 里定义了 BuildArtifactsDir 元素,并用 Include 属性指定 BuildArtifacts 目录。记得 BuildArtifacts 目录后面要有个斜杠。最后,我们用了 @(ItemName) 语法在 Target 里面引用这个目录。现在如果要修改目录名的话,只需要改 BuildArtifactsDir 的 Include 属性就好了。

接下来还有个问题要处理。在 BuildArtifacts 目录已经存在的情况下,Init 是什么事都不干的。也是就说,在调用 Init 的时候磁盘上的已有文件还会被保留下来。这一点着实不妥,如果能每次调用 Init 的时候,都把目录和目录里面的所有文件都一起删掉再重新创建,就能保证后续环节都在干净的环境下执行了。我们固然可以在每次调用 Init 的时候先手工调一下 Clean,但给 Init Target 加一个 DependsOnTargets 属性会更简单,这个属性会告诉 MSBuild,每次执行 Init 的时候都先执行 Clean。

<Target Name="Init" DependsOnTargets="Clean">
 <MakeDir Directories="@(BuildArtifactsDir)" />
</Target>

现在 MSBuild 会帮我们在调 Init 之前先调 Clean 了。跟 DependsOnTargets 这个属性所暗示的一样,一个 Target 可以依赖于多个 Target,之间用分号分割就行。

接下来我们要编译应用程序,把编译后的结果放到 BuildArtifacts 目录下。先写一个 Compile Target,让它依赖于 Init。这个 Target 会调用另一个 MSBuild 实例来编译应用。我们把 BuildArtifacts 目录传进去,作为编译结果的输出目录。

<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>

上面的脚本做了几件事情。首先,ItemGroup 添加了另一个 Item,叫做 SolutionFile,它指向 solution 文件。在构建脚本中用 Item 或 Property 代替硬编码,这算的是一个优秀实践吧。

其次,我们创建了一个 PropertyGroup,里面包含两个 Property:Configuration 和 BuildPlatform。它们的值分别是“Release”和“Any CPU”。当然,Property 也可以在运行时通过 /property(简写为 /p)赋值。我们还用了 Condition 属性,它在这里的含义是,只有当这两个属性没有值的情况下,才用我们定义的数据给它们赋值。这段代码实际上就是给它们一个默认值。

接下来就是 Compile Target 了,它依赖于 Init,里面内嵌了一个 MSBuild Task。它在运行的时候会调用另外一个 MSBuild 实例。在脚本中定义了这个被内嵌的 MSBuild Task 要操作的项目。在这里,我们既可以传入另外一个 MSBuild 脚本,也可以传入.csproj 文件(它本身也是个 MSBuild 脚本)。但我们选择了传入 HelloCI 应用的 solution 文件。Solution 文件不是 MSBuild 脚本,但是 MSBuild 可以解析它。脚本中还指定了内嵌的 MSBuild Task 要执行的 Target 名称:“Rebuild”,这个 Target 已经被导入到 solution 的.csproj 文件中了。最后,我们给内嵌的 Task 传入了三个 Property。

OutDir

          编译结果的输出目录

Configuration

          构建(调试、发布等)时要使用的配置

Platform

          编译所用的平台(x86、x64 等)

给上面这三个 Property 赋值用的就是先前定义的 Item 和 Property。OutDir Property 用的是 BuildArtifacts 目录的全路径。这里用了 %(Item.MetaData) 语法。这个语法应该看起来很眼熟吧?就跟访问 C# 对象属性的语法一样。MSBuild 创建出来的任何 Item,都提供了某些元数据以供访问,例如 FullPath 和 ModifiedTime。但这些元数据有时候也没啥大用,因为 Item 不一定是文件。

Configuration 和 Platform 用到了先前定义好的 Property,语法格式是 $(PropertyName)。在这里可以看到系统保留的一些属性名,用户不能更改。定义 Property 的时候请不要用它们。

这里还有些东西值得提一下。用了 Property 以后,我们可以在不更改构建脚本的情况下使用不同的 Configuration 或者 BuildPlatform,只要在运行的时候用 /property 传值进去就行。所以“msbuild HelloCI.msbuild /t:Compile /p:Configuration:Debug”这个命令会用 Debug 配置构建项目,而“msbuild HelloCI.msbuild /t:Compile /p:Configuration:Test;BuildPlatform:x86”会在 x86 平台下使用 Test 配置。

现在运行 Compile,就可以编译 solution 下的两个项目,把编译结果放到 BuildArtifacts 目录下。在完成构建脚本之前,只剩下最后一个 Target 了:

<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 里现在又多了三个 Item:NUnitConsole 指向 NUnit 控制台运行器(console runner);UnitTestDLL 指向单元测试项目生成的 DLL 文件;TestResultsPath 是要传给 NUnit 的,这样测试结果就会放到 BuildArtifacts 目录下。

RunUnitTests Target 用到了 Exec Task。如果有一个测试运行失败,NUnit 控制台运行器会返回一个非 0 的结果。这个返回值会告诉 MSBuild 有个地方出错了,于是整个构建的状态就是失败。

现在这个脚本比较完善了,用一个命令就可以删除旧的构建产物、编译、运行单元测试:

C:\HelloCI\> msbuild HelloCI.msbuild /t:RunUnitTests

我们还可以给脚本设一个默认 Target,就省得某次都要指定了。在 Project 元素上加一个 DefaultTargets 属性,让 RunUnitTests 成为默认 Target。

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
                DefaultTargets="RunUnitTests">

你还可以创建自己的 Task。这里有个例子,AsyncExec,它允许人们以异步的方式执行命令。比如有个 Target 用来启动 Web 服务器,要是用 Exec 命令的话,整个构建都会停住,直到服务器关闭。用 AsyncExec 这个命令可以让构建继续执行,不用等待命令执行结束。

本文的完整脚本可以在这里下载。

在接下来的文章中,我会讲述如何配置 Jenkins。我们不再需要手动运行命令来构建整个项目,Jenkins 会检测代码库,一旦有更新就会自动触发构建。

更多详情见请继续阅读下一页的精彩内容 :http://www.linuxidc.com/Linux/2016-06/132287p2.htm

与 Jenkins 相会

Jenkins 在 2004 年由 Kohsuke Kawaguchi 创建,最开始的名字叫 Hudson。Kawaguchi 当时在 Sun 工作,Sun 直到 2008 年一直为 Hudson 提供支持,但是当它被 Oracle 收购以后,Oracle 和 Hudson 的开发者社区出现了分歧。2011 年,Oracle 宣称了对 Hudson 商标的所有权,Hudson 开发社区于是将 Hudson 改名叫 Jenkins,摆脱了 Oracle 的干扰,继续开发工作。

第一眼看上去,Jenkins 像是一个专为 Java 项目准备的持续集成工具:有为 Maven 项目设计的 job,有诸多为 Java 项目提供的默认插件,更不要说那些用 Java 写的插件了。但 Jenkins 其实是一个非常灵活的工具,它可以结合各种版本管理系统和构建工具,用来构建任何类型的项目。在这篇文章中,我们会利用它的灵活性,从 Mercurial 中 pull 代码,用 MSbuild 构建项目。首先,我们需要下载并安装 Jenkins,然后安装 Mercurial 和 MSBuild 插件。

配置 Jenkins

从官方网站下载安装程序。它的 Windows 安装包很简单,会把 Jenkins 安装成 Windows 服务。Jenkins 的默认的访问路径是 http://localhost:8080,请确保 8080 端口不要被其他应用占用。

安装完毕以后就是装插件。请点击“Manage Jenkins”链接,然后再点击“Manage Plugins”。在“Available”标签页可以查看当前可安装的插件──你需要有一个能上网的环境,才能看到这一页的内容。用 filter 找到 Mercurial 和 MSBuild 插件,点击插件名称前面的单选框,进行安装。你可以点击“Installed”标签页,来确保这两个插件已经被安装成功了。在安装过程中,你可能会看到提示信息说 Jenkins 需要重启才能完成安装,请让它重启,等重启完成后再访问“Installed”标签页,看看是不是安装成功。

 

Mercurial 插件还需要做一些配置,才能让 Jenkins 找到 Mercurial 的安装路径。回到“Manage Jenkins”这一页上来,点击“Configure System”,找到“Mercurial”这一部分──如果你找不到“Mercurial”的话,就说明 Mercurial 插件没有装好──点击“Add Mercurial”按钮之后,你需要给这个 Mercurial 实例起个名字(自己用着越方便越好);还需要输入 Mercurial 可执行文件的安装路径,这里用的是 hg.exe 文件的所在目录;最后是可执行文件的名字,就用“INSTALLATION/hg.exe”就好,INSTALLATION 这个单词会被前面输入的安装路径替换掉。

创建一个 Jenkins Job

点击“Back to Dashboard”,回到 Dashboard 上来,然后点击“New Job”链接。你会看到一组 job 类型,选择“Build a free-style software project”,给它命名为“HelloCI-RunUnitTests”,点击 OK。

下一步是 job 配置页面。这一页有很多配置项,而且大多数都带有详细的描述信息,点击右侧的帮助图标就可以看到。我们现在只配置两部分,一是代码库所在位置,二是如何用 MSBuild 构建项目。

找到“Source Code Management”,选择 Mercurial。在“Mercurial Version”输入框中输入先前在“Configure System”中配置的 Mercurial 名称。然后在 Repository URL 中输入 Mercurial 版本库的 URL(也可以是一个文件系统的路径)。最后在 Branch 中输入你想跟踪的分支名。

接下来到“Build”这部分。点击“Add build step”按钮后,下拉框中就会出现一系列的 step 类型以供选择,其中便包括“Build a Visual Studio project or solution using MSBuild”,如果你没看到这个选项,就说明 MSBuild 插件没有正确安装。

点击“Build a Visual Studio project or solution using MSBuild”之后,在“MSBuild Build File”输入框中输入构建脚本的名字:HelloCI.msbuild。我们想让 Jenkins 执行“RunUnitTests”这个 Target,如果你没有把 DefaultTargets 属性设成 RunUnitTests 的话,可以在“Command Line Arguments”中输入“/t:RunUnitTests”,其中 / t 是 /target 的简写。

点击“Save”或“Apply”保存之后,这个 job 一旦被触发,就可以 pull 代码下来,编译项目,执行单元测试。我们先来手工触发一次,看看配置是否正确。先回到 Dashboard,这时可以在屏幕中央看到我们的 job。点击 job 名字,然后在左侧的链接中找到“Build Now”链接,点击它,Jenkins 就会开始执行。在这组链接的下方有一个“Build History”列表,它显示的是这个 job 的所有构建历史,当第一次构建开始运行的时候,你会在列表中看到一个进度条,同时还有一个小圆球显示构建状态。圆球闪烁表示构建正在进行中,它停止闪烁的时候一般会是红色或蓝色,红色表示构建失败,蓝色表示成功。

如果这个 job 能够访问 Mercurial 版本库,找到了 HelloCI.msbuild 脚本,“RunUnitTest”执行成功,这个圆球应该会变蓝。这时候你也就顺利完成了第一个 Jenkins 构建。如果构建失败,请点击“Build History”对应的编号查看详细信息,然后点击“Console Output”,就可以看到 Jenkins 所执行的每一个命令和对应结果,从中可以分析出构建失败的原因。

触发构建

构建成功以后,下一步要做的就是让 Jenkins 检测版本库的变化,一旦有代码提交,Jenkins 就要 pull 代码并执行构建。有好几种方法可以做到这一点。最简单的就是让 Jenkins 定时构建,但是如果在这一段时间内没有代码提交,这次构建反而是浪费。另一种方式是让 Jenkins 定时轮询,看看版本库中是否有代码提交。这种方法的缺点是当有了代码提交以后,Jenkins 要等到下一个轮询周期才能执行构建。当然,你也可以让 Jenkins 每分钟都轮询一次,尽可能缩短等待时间,但我们还有另一种更优雅的方案──给 Mercurial 版本库中放一个 post-commit 的钩子,这样一旦版本库接受了新代码,它都会通知 Jenkins,让它立刻开始构建。

这个方法需要在.hg 目录下的配置文件中(.hgrc)添加一个钩子。在这个钩子里面,你需要让 Mercurial 访问下面这个网址:http://localhost:8080/job/JOBNAME/build?delay=0sec(JOBNAME 需要替换成真实的 job 名称),让 Jenkins 启动构建。但不幸的是,Windows 下没有类似 Linux wget 这样可以进行 HTTP 请求的工具。我自己写了行 Ruby 代码来做这项工作。你也可以用 Powershell 来创建一个 System.Net.WebClient 的实例,然后调用 DownloadString 方法。读者朋友可以自己完成这项工作,权当练习吧。

如果你不愿意用这个方法,就还用轮询吧。回到 job 配置页面,在“Build Trigger”区域选择“Poll SCM”,在“Schedule”输入框中输入轮询周期。它采用的语法格式是 cron 的风格。如果要每分钟轮询一次,就输入“1 * * * *”。你可以点击输入框右侧的帮助按钮,查看轮询周期的语法介绍。

构建流水线

Jenkins 可以在某个构建成功结束之后启动其他 job。于是就有了构建流水线,它的概念就是一个 job 成功之后触发其他 job。触发者叫做上游 job,被触发者被称作下游 job。

构建流水线的应用场景有很多:让耗时较长的测试在单元测试结束之后执行;运行静态代码检查;把构建结果部署到试机环境(staging)或者产品环境中。我们下面来演示一下这个功能,让 Jenkins 在构建结束后启动 web 服务器,运行 HelloCI 这款应用。

我们只需要做三件事情:HelloCI-RunUnitTests job 成功之后触发一个新 job;把 HelloCI-RunUnitTests 的构建结果拷贝出来;启动 web 服务器。在开始之前,你还需要安装 Copy Artifacts 插件。回到 Manage Plugins 页面,参考之前安装 Mercurial 插件的方法安装 Copy Artifacts。在看到重启的提示信息时重启 Jenkins。

创建新 job 之前,我们需要告诉 HelloCI-RunUnitTests job,让它把构建产物保存下来,以供新 job 使用。回到 HelloCI-RunUnitTests job 的配置界面,找到“Post-build Action”,选中“Archive the artifacts”,页面上就会出现一个文本框:“Files to archive”。我们要存档的目录有两份,一份是 BuildArtifacts 目录,一份是 packages 目录,后者是为了让我们能够访问 NuGet package。如果要指定某个目录以及目录下所有内容,就需要在目录后面跟一个斜杠和两个星号。不同的目录或文件之间用逗号分割。在这里我们输入的是“BuildArtifacts/**,packages/**”。

现在创建一个新 job,起名叫“HelloCI-StartWebServer”。在配置页面上“Build Triggers”那一节里选中“Build after other projects are built”,把之前那个 job 配置成要触发当前 job 的项目。

接下来给 job 添加一个 build step,让这个 job 所做的第一件事情就是拷贝之前 job 所保存的构建产物。这里要用到“Copy artifacts from another project”这个 step(它是由 Copy Artifacts 插件提供的)。我们只需要填入 HelloCI-RunUnitTests job 的名字就可以。

最后还要添加一个 build step,让它启动 web server。这里用的是“Execute Windows batch command”,我们用 Visual Studio 提供的 Cassini 来运行应用。Cassini 可以在“C:\Program Files (x86)\Common Files\microsoft shared\DevServer\10.0\WebDev.WebServer40.EXE”找到。运行 Cassini 很简单,把 web 应用存放的路径作为参数传给它就行,如果没有指定端口号的话,它就会使用默认的端口号 80。

“Execute Windows batch command”step 需要一个可以在 BuildArtifacts 和 packages 拷贝后的目录下执行的命令。我们把下面这个命令复制到“Command”输入框中:

"C:\Program Files (x86)\Common Files\microsoft shared\DevServer\10.0\WebDev.WebServer40.
EXE" /port:9876 /path BuildArtifacts/_PublishedWebsites/HelloCI.Web/.

这个命令可以启动 Cassini,把它指向拷贝过来的应用,然后在 9876 端口启动服务器。

所有的配置都已就绪。试着修改一下代码然后提交。Jenkins 应该能够监听到变化,运行 HelloCI-RunUnitTests。如果代码编译成功,所有测试均以通过,Jenkins 应该开始运行 HelloCI-StartWebServer job,把 HelloCI-RunUnitTests 的构建产物拷贝过来,在 9876 端口启动 Cassini。如果这一切都运行无误,我们的持续集成系统就搭好了。

结尾

MSBuild 和 Jenkins 可讲的地方还很多。比如说可以从持续集成走��持续部署。你所需要的只是一个 job,让它把构建产物部署到生产环境(译者注:持续部署殊非易事,作者有些夸大其词,读者请勿轻信)。ASP.NET 的应用就更简单了,用一个 job 把构建产物复制到某个目录下就行。这样一来,只要所有的测试全都通过,这个 Jenkins job 就可以把最新特性发布到生产环境上去。

如果你还没做好准备走那么远,也可以用一下 Promoted Build plugin,它可以在某个构建达到了一个特定的标准之后,把它标记为“推荐使用”。一个人或几个人手工审核过一个构建,也算得是达到了推荐标准。有了 Promoted Build plugin,人们做出的修改就可以自动发布到开发服务器上,所有的新功能都集中呈现。开发经理而后可以把某个特定的构建推荐到试机环境中。QA 团队或其他干系人可以进行验收(其方式就是推荐试机环境上的构建),然后这个构建就可以由 Jenkins 部署到产品环境上。

John Ferguson Smart 曾写过一本很出色的书,叫做“Jenkins: The Definitive Guide”。这本书除了讲述 Jenkins 的功能之外,还描述了怎么保证 Jenkins 的安全,怎样检测资源使用(如磁盘占用),怎样进行分布式构建,直到设置通知系统,让用户在构建开始、成功、失败的时候得到通知。这本书由 O ’Reilly 出版,购买地址在此。如果你对 Jenkins 还不太了解的话,本书就属于必看的一本。

拥有一个自动化的构建部署系统,就跟要看重测试,要使用版本控制系统一样重要。我希望你已经熟练掌握了如何设置持续集成系统,可以开始着手集成自己的系统。

一开始不要太激进,一心想着把所有事情都做好。你可以一点一点来。你终会享受到胜利果实的。当 Jenkins 通知你某个构建失败的时候,你发现 bug 的时机就提前了;当你再也不用记忆每一步部署的操作时,你就有了一整套系统化的部署流程。而更重要的是,你有了更多时间去做更重要的事情,比如开发有价值的特性,或是为新项目做准备,也可以提前下班回家,再也不用为即将到来的上线担忧。

Jenkins 的详细介绍 :请点这里
Jenkins 的下载地址 :请点这里

本文永久更新链接地址 :http://www.linuxidc.com/Linux/2016-06/132287.htm

你或其他人刚刚写完了一段代码,提交到项目的版本仓库里面。但等一下,如果新提交的代码把构建搞坏了怎么办?万一出现编译错误,或者有的测试失败了,或者代码不符合质量标准所要求的底限,你该怎么办?

最不靠谱的解决方案就是寄希望于所有人都是精英,他们根本不会犯这些错误。但如果真的出现了这些问题,我们就希望发现的越早越好。最好的方式就是只要有代码提交,我们就有某种方式对它进行验证。这就是持续集成的作用。

持续集成相关的工具有很多。最流行的要数一款基于 Java 的名叫 Jenkins 的工具。它提供了 Web 界面,用户可以在界面上配置 Job,每个 Job 都包含一系列的构建步骤。Jenkins 可以完成开头那个场景中所提到的所有验证工作,它还能更进一步做自动化部署或者一键式部署。

Jenkins 是由 Sun 的前员工开发的,它的根基是 Java,但也可以用在非 Java 的项目里,比如 PHP、Ruby on Rails、.NET。在.NET 项目里,你除了 Jenkins 之外还要熟悉另一样工具:MSBuild。

Visual Studio 用 MSBuild 构建.NET 项目。MSBuild 所需的仅仅是一个脚本,在脚本中指定要执行的 target。项目中的 .csproj 和 .vbproj 文件都是 MSBuild 脚本。

在这篇文章中,我们会从头开始,一步步完成一个属于我们自己的 MSBuild 脚本。在它完成以后,我们只需要一个命令就可以删除之前的构建产物,构建.NET 应用,运行单元测试。后面我们还会配一个 Jenkins Job,让它从代码库中更新代码,执行 MSBuild 脚本。最后还会配另一个 Jenkins Job,让它监听第一个 Job 的结果,当第一步成功以后,它会把相关的构建产物复制出来,放到 web 服务器里启动运行。

我们用一个 ASP.NET MVC 3 应用做例子,在 VS 里面创建 ASP.NET MVC 3 应用并选择“application”模版就行。我们还要用一个单元测试项目来跑测试。代码可以在这里下载。

局域网内利用 GitLab+Jenkins 自动生成 GitBook 并发布 (Nginx)  http://www.linuxidc.com/Linux/2016-05/131136.htm

Linux+Git+Maven+Jenkins+Neuxs 自动化编译环境搭建 http://www.linuxidc.com/Linux/2016-02/128652.htm

CentOS6 安装 Jenkins  http://www.linuxidc.com/Linux/2016-05/131365.htm

使用 Jenkins 配置 Git+Maven 的自动化构建 http://www.linuxidc.com/Linux/2016-02/128641.htm

Jenkins+Maven+Git 搭建持续集成和自动化部署的配置手记 http://www.linuxidc.com/Linux/2015-06/118606.htm

Jenkins 的分布式构建及部署——节点  http://www.linuxidc.com/Linux/2015-05/116903.htm

你好,MSBuild

MSBuild 是在.NET 2.0 中引入的针对 Visual Studio 的构建系统。它可以执行构建脚本,完成各种 Task──最主要的是把.NET 项目编译成可执行文件或者 DLL。从技术角度来说,制作 EXE 或者 DLL 的重要工作是由编译器(csc,vbc 等等)完成的。MSBuild 会从内部调用编译器,并完成其他必要的工作(例如拷贝引用──CopyLocal,执行构建前后的准备及清理工作等)

这些工作都是 MSBuild 执行脚本中的 Task 完成的。MSBuild 脚本就是 XML 文件,根元素是 Project,使用 MSBuild 自己的命名空间。MSBuild 文件都要有 Target。Target 由 Task 组成,MSBuild 运行这些 Task,完成一个完整的目标。Target 中可以不包含 Task,但是所有的 Target 都要有名字。

下面来一起创建一个“Hello World”的 MSBuild 脚本,先保证配置正确。我建议用 VS 来写,因为它可以提供 IntelliSense 支持,不过用文本编辑器也无所谓,因为只是写个 XML 文件,IntelliSense 的用处也不是很大。先创建一个 XML 文件,命名为“basics.msbuild”,这个扩展名只是个约定而已,好让我们容易认出这是个 MSBuild 脚本,你倒不用非写这样的扩展名。给文件添加一个 Project 元素作为根元素,把 http://schemas.microsoft.com/developer/msbuild/2003 设置成命名空间,如下所示

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
</Project>

下一步,给 Project 元素添加一个 Target 元素,起名叫“EchoGreeting”

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="EchoGreeting" />
</Project>

这就行了。我们已经有了一个可以运行的 MSBuild 脚本。它虽然还啥事都没干,但我们可以用它来验证当前环境是不是可以运行 MSBuild 脚本。在运行脚本的时候,我们要用到.NET 框架安装路径下的 MSBuild 可执行文件。打开命令行,执行“MSBuild /nologo /version”命令,看看.NET 框架安装路径是不是放到了 PATH 环境变量里面。如果一切正确,你应该能看到屏幕上打印出 MSBuild 的当前版本。倘若没有的话,或者把.NET 框架安装路径放到 PATH 里面去,或者直接用 Visual Studio Command Prompt,它已经把该配的都配好了。

进入存放刚才那个脚本的目录后,以文件名当作参数调用 MSBuild,就可以执行脚本了。在我的机器上可以看到下面的执行结果:

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

执行完脚本以后,MSBuild 会首先显示一个启动界面和版权信息(用 /nologo 开关可以隐藏掉它们)。接下来会显示一个启动时间,然后便是真正的构建过程。因为咱们的脚本啥都没干,所以构建就直接成功了。总计用时也会显示在界面上。下面咱们来给 EchoGreeting Target 添加一个 Task,让脚本真的干点事。

<Target Name="EchoGreeting">
    lt;Exec Command="echo Hello from MSBuild" />
</Target>

现在 EchoGreeting Target 有了一个 Exec Task,它会执行 Command 属性中定义的任何命令。再运行一次脚本,你应该能看到更多信息了。在大多数时候,MSBuild 的输出信息都很长,你可以用 /verbosity 开关来只显示必要信息。不过无论怎样,MSBuild 都会把我们的文字显示到屏幕上。下面再添加一个 Target。

<Target Name="EchoDate">
 <Exec Command="echo %25date%25" />
</Target>

这个 Target 会输出当前日期。它的命令要做的事情就是“echo %25date%25”,但是“%”字符在 MSBuild 中有特殊含义,所以这个命令需要被转义。当遇到转义字符的时候,“%”后面的十进制字符会被转成对应的 ASCII 码。MSBuild 只会执行 Project 元素中的第一个 Target。要执行其他 Target 的时候,需要把 /target 开关(可简写为 /t)加上 Target 名称传给 MSBuild。你也可以指定 MSBuild 执行多个 Target,只要用分号分割 Target 名字就可以。

C:\>msbuild basics.msbuild /nologo /verbosity:minimal /t:EchoGreeting;EchoDate
 Hello from MSBuild
 Thu 08/02/2012

更实用的构建脚本

演示就先到这里。下面来用 MSBuild 来构建一个真实项目。首先把示例代码下载下来,或是自己创建一个 ASP.NET 应用。给它添加一个 MSBuild 脚本,以 solution 或 project 名字给脚本命名,扩展名用“.msbuild”。照先前一样指定 MSBuild 命名空间。

开始写脚本之前,先把脚本要干的事情列出来:

  1. 创建 BuildArtifacts 目录
  2. 构建 solution,把构建产物(DLL,EXE,静态内容等等)放到 BuildArtifacts 目录下。
  3. 运行单元测试。

因为示例应用叫做 HelloCI,于是这个脚本也就命名为 HelloCI.msbuild。先添加命名空间,然后就可以添加第一个 Target 了,我管它叫做 Init。

<Target Name="Init">
 <MakeDir Directories="BuildArtifacts" />
</Target>

这个 Target 会调用 MakeDir Task 创建一个新的目录,名叫 BuildArtifacts,跟脚本在同一目录下。运行脚本,你会发现该目录被成功创建。如果再次运行,MSBuild 就会跳过这个 Task,因为同名目录已经存在了。

接下来写一个 Clean Target,它负责删除 BuildArtifacts 目录和里面的文件。

<Target Name="Clean">
 <RemoveDir Directories="BuildArtifacts" />
</Target>

理解了 Init 之后,这段脚本就应该很好懂了。试着执行一下,BuildArtifacts 目录应该就被删掉了。下面再来把代码中的重复干掉。在 Init 和 Clean 两个 Target 里面,我们都把 BuildArtifacts 的目录名硬编码到代码里面了,如果未来要修改这个名字的话,就得同时改两个地方。这里可以利用 Item 或 Property 避免这种问题。

Item 和 Property 只有些许差别。Property 由简单的键值对构成,在脚本执行的时候还可以用 /property 赋值。Item 更强大一些,它可以用来存储更复杂的数据。我们这里不用任何复杂数据,但需要用 Items 获取额外的元信息,例如文件全路径。

接下来修改一下脚本,用一个 Item 存放路径名,然后修改 Init 和 Clean,让它们引用这个 Item。

<ItemGroup>
 <BuildArtifactsDir Include="BuildArtifacts\" />
</ItemGroup>

<Target Name="Init">
 <MakeDir Directories="@(BuildArtifactsDir)" />
</Target>

<Target Name="Clean">
 <RemoveDir Directories="@(BuildArtifactsDir)" />
</Target>

Item 是在 ItemGroup 里面定义的。在一个 Project 中可以有多个 ItemGroup 元素,用来把有关系的 Item 分组。这个功能在 Item 较多的时候特别有用。我们在 ItemGroup 里定义了 BuildArtifactsDir 元素,并用 Include 属性指定 BuildArtifacts 目录。记得 BuildArtifacts 目录后面要有个斜杠。最后,我们用了 @(ItemName) 语法在 Target 里面引用这个目录。现在如果要修改目录名的话,只需要改 BuildArtifactsDir 的 Include 属性就好了。

接下来还有个问题要处理。在 BuildArtifacts 目录已经存在的情况下,Init 是什么事都不干的。也是就说,在调用 Init 的时候磁盘上的已有文件还会被保留下来。这一点着实不妥,如果能每次调用 Init 的时候,都把目录和目录里面的所有文件都一起删掉再重新创建,就能保证后续环节都在干净的环境下执行了。我们固然可以在每次调用 Init 的时候先手工调一下 Clean,但给 Init Target 加一个 DependsOnTargets 属性会更简单,这个属性会告诉 MSBuild,每次执行 Init 的时候都先执行 Clean。

<Target Name="Init" DependsOnTargets="Clean">
 <MakeDir Directories="@(BuildArtifactsDir)" />
</Target>

现在 MSBuild 会帮我们在调 Init 之前先调 Clean 了。跟 DependsOnTargets 这个属性所暗示的一样,一个 Target 可以依赖于多个 Target,之间用分号分割就行。

接下来我们要编译应用程序,把编译后的结果放到 BuildArtifacts 目录下。先写一个 Compile Target,让它依赖于 Init。这个 Target 会调用另一个 MSBuild 实例来编译应用。我们把 BuildArtifacts 目录传进去,作为编译结果的输出目录。

<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>

上面的脚本做了几件事情。首先,ItemGroup 添加了另一个 Item,叫做 SolutionFile,它指向 solution 文件。在构建脚本中用 Item 或 Property 代替硬编码,这算的是一个优秀实践吧。

其次,我们创建了一个 PropertyGroup,里面包含两个 Property:Configuration 和 BuildPlatform。它们的值分别是“Release”和“Any CPU”。当然,Property 也可以在运行时通过 /property(简写为 /p)赋值。我们还用了 Condition 属性,它在这里的含义是,只有当这两个属性没有值的情况下,才用我们定义的数据给它们赋值。这段代码实际上就是给它们一个默认值。

接下来就是 Compile Target 了,它依赖于 Init,里面内嵌了一个 MSBuild Task。它在运行的时候会调用另外一个 MSBuild 实例。在脚本中定义了这个被内嵌的 MSBuild Task 要操作的项目。在这里,我们既可以传入另外一个 MSBuild 脚本,也可以传入.csproj 文件(它本身也是个 MSBuild 脚本)。但我们选择了传入 HelloCI 应用的 solution 文件。Solution 文件不是 MSBuild 脚本,但是 MSBuild 可以解析它。脚本中还指定了内嵌的 MSBuild Task 要执行的 Target 名称:“Rebuild”,这个 Target 已经被导入到 solution 的.csproj 文件中了。最后,我们给内嵌的 Task 传入了三个 Property。

OutDir

          编译结果的输出目录

Configuration

          构建(调试、发布等)时要使用的配置

Platform

          编译所用的平台(x86、x64 等)

给上面这三个 Property 赋值用的就是先前定义的 Item 和 Property。OutDir Property 用的是 BuildArtifacts 目录的全路径。这里用了 %(Item.MetaData) 语法。这个语法应该看起来很眼熟吧?就跟访问 C# 对象属性的语法一样。MSBuild 创建出来的任何 Item,都提供了某些元数据以供访问,例如 FullPath 和 ModifiedTime。但这些元数据有时候也没啥大用,因为 Item 不一定是文件。

Configuration 和 Platform 用到了先前定义好的 Property,语法格式是 $(PropertyName)。在这里可以看到系统保留的一些属性名,用户不能更改。定义 Property 的时候请不要用它们。

这里还有些东西值得提一下。用了 Property 以后,我们可以在不更改构建脚本的情况下使用不同的 Configuration 或者 BuildPlatform,只要在运行的时候用 /property 传值进去就行。所以“msbuild HelloCI.msbuild /t:Compile /p:Configuration:Debug”这个命令会用 Debug 配置构建项目,而“msbuild HelloCI.msbuild /t:Compile /p:Configuration:Test;BuildPlatform:x86”会在 x86 平台下使用 Test 配置。

现在运行 Compile,就可以编译 solution 下的两个项目,把编译结果放到 BuildArtifacts 目录下。在完成构建脚本之前,只剩下最后一个 Target 了:

<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 里现在又多了三个 Item:NUnitConsole 指向 NUnit 控制台运行器(console runner);UnitTestDLL 指向单元测试项目生成的 DLL 文件;TestResultsPath 是要传给 NUnit 的,这样测试结果就会放到 BuildArtifacts 目录下。

RunUnitTests Target 用到了 Exec Task。如果有一个测试运行失败,NUnit 控制台运行器会返回一个非 0 的结果。这个返回值会告诉 MSBuild 有个地方出错了,于是整个构建的状态就是失败。

现在这个脚本比较完善了,用一个命令就可以删除旧的构建产物、编译、运行单元测试:

C:\HelloCI\> msbuild HelloCI.msbuild /t:RunUnitTests

我们还可以给脚本设一个默认 Target,就省得某次都要指定了。在 Project 元素上加一个 DefaultTargets 属性,让 RunUnitTests 成为默认 Target。

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
                DefaultTargets="RunUnitTests">

你还可以创建自己的 Task。这里有个例子,AsyncExec,它允许人们以异步的方式执行命令。比如有个 Target 用来启动 Web 服务器,要是用 Exec 命令的话,整个构建都会停住,直到服务器关闭。用 AsyncExec 这个命令可以让构建继续执行,不用等待命令执行结束。

本文的完整脚本可以在这里下载。

在接下来的文章中,我会讲述如何配置 Jenkins。我们不再需要手动运行命令来构建整个项目,Jenkins 会检测代码库,一旦有更新就会自动触发构建。

更多详情见请继续阅读下一页的精彩内容 :http://www.linuxidc.com/Linux/2016-06/132287p2.htm

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-21发表,共计23322字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中