上一节 Jenkins+MSBuild+Svn搭建持续集成环境Part 1: MSBuild 对MSBuild做了简单介绍,那么这一节就针对实际的项目来创建MSBuild构建脚本。对于简单的小项目,也许直接通过 msbuild xxxx.csproj
就能得到你想要的结果,但是对于复杂的项目,那就需要根据你想要的构建结果去创建相应的构建脚本。
我们公司的站点用的是一个叫NopCommerce的框架,NopCommerce是国外的一个高质量的开源b2c网站系统,具有很强的插件机制,功能很强大,很适合用来做外贸系统和二次开发。
整个项目的结构大体是这样的:
红框里面的,就是整个项目的核心部分。
之前我每次发布系统都是直接在Visual Studio里面对Nop.Web这个项目右键点发布,发布之后结构是这样的:
然后我一般会删掉一些不必要的文件夹和文件,比如App_Data整个文件夹删掉,然后Plugins一般没改动我也会删掉,最后,压缩上传服务器替换发布。这一整个流程下来,要耗费我很多时间,特别需要频繁发布测试的时候,一天下来其它活儿都不用干了,就等发布就行了。所以,才会考虑用Jenkins和MSBuild做自动化发布。然后我尝试了直接使用msbuild
命令对NopCommerce.sln文件和Nop.Web.csproj文件直接进行构建,但是都得不到我想要的结果,要不就是少了文件不然就是文件夹结构不对。在网上也基本找不到使用MSBuild构建NopCommerce的例子。根据NopCommerce官方团队成员的说法,构建必须得按照以下要求:
- Nop.Web and Nop.Admin are two web applications. They both need to be published to the same directory. “Nop.Web” to “Published\Web\”. “Nop.Admin” to “Published\Web\Administration\”. Make sure the .dlls from Nop.Admin (Published\Web\Administration\bin) are moved and exist in (Published\Web\bin).
- Ensure that plugins (\Presentation\Nop.Web\Plugins) are copied into \Published\Web\Plugins\ directory
- Select all the files in Published\Web\ directory and upload them to your web server.
所以,我决定一步步的去写构建脚本,然后得到我想要的结果。
Step 1 | 创建一个MSBuild XML文件
首先创建一个文件,命名为NopCommerce.msbuild
,这个扩展名只是方便我们认出它是一个MSBuild脚本,也可以不写扩展名,然后往这个文件里面添加一个Project
作为根元素:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
</Project>
Step 2 | 添加一组Properties:
往Project
里面添加一组PropertyGroup
:
<PropertyGroup>
<RootFolder>$(MSBuildProjectDirectory)</RootFolder>
<DeployBranch Condition="'$(DeployBranch)' == ''">qa</DeployBranch>
<DeployFolder Condition="'$(DeployFolder)' == ''">$(RootFolder)\Release</DeployFolder>
<WebFolder>$(RootFolder)\$(DeployBranch)\Presentation\Nop.Web</WebFolder>
<Configuration Condition="'$(Configuration)' == ''">Release</Configuration>
<Platform Condition="'$(Platform)' == ''">Any CPU</Platform>
</PropertyGroup>
RootFolder
是根目录,DeployBranch
发布分支,DeployFolder
输出文件夹,默认为根目录底下的Release
文件夹,WebFolder
Nop.Web的文件夹,Configuration
默认为Release
版本,Platform
默认Any CPU
。
Step 3 | 添加一个Target
这个Target
命名为Deploy
,它将包含我们接下来要做的所有Task
,包括创建文件夹,生成项目,删除不必要的文件,移动文件等:
<Target Name="Deploy">
</Target>
Step 4 | 删除并创建发布文件夹
首先,将之前发布过的历史文件夹先删除再创建,确保输出目录是干净的
<!--删除发布文件夹-->
<RemoveDir Directories="$(DeployFolder)" />
<!-- 创建发布文件夹 -->
<MakeDir Directories="$(DeployFolder)" />
Step 5 | 添加生成主程序的Task
生成Nop.Web
和Nop.Admin
,这两个项目分别是网站的前台和后台,然后配置web文件的输出目录,和bin目录文件夹,Configuration
根据传入的配置决定,可以是Release
或者是Debug
版本:
<!--开始发布 Nop.Web-->
<MSBuild Projects="$(RootFolder)\$(DeployBranch)\Presentation\Nop.Web\Nop.Web.csproj"
Targets="ResolveReferences;_CopyWebApplication"
Properties="WebProjectOutputDir=$(DeployFolder)\;
OutDir=$(DeployFolder)\bin\;Configuration=$(Configuration)" />
<!--开始发布 Nop.Admin-->
<MSBuild Projects="$(RootFolder)\$(DeployBranch)\Presentation\Nop.Web\Administration\Nop.Admin.csproj" Targets="ResolveReferences;_CopyWebApplication"
Properties="WebProjectOutputDir=$(DeployFolder)\Administration\;
OutDir=$(DeployFolder)\Administration\bin\;Configuration=$(Configuration)" />
Step 6 | 删除不必要的文件与文件夹
一般来说有很多config文件在发布的时候是不会覆盖的,比如测试环境和生产环境的配置一般不同,如果覆盖了就会出现问题,还有其实Administration文件夹里面只需要Contents跟Views,其他都是不需要的,所以我们把它删掉:
<!--删除Nop.admin里面不必要的文件夹-->
<RemoveDir Directories="$(DeployFolder)\Administration\bin\;
$(DeployFolder)\Administration\App_Data\;
$(DeployFolder)\Administration\Collection\;
$(DeployFolder)\Administration\Scripts\;
$(DeployFolder)\Administration\Themes\;
$(DeployFolder)\App_Data\;" />
<!--删除不必要的文件-->
<Delete Files="$(DeployFolder)\Administration\packages.config;
$(DeployFolder)\packages.config;
$(DeployFolder)\Web.config;
$(DeployFolder)\favicon.ico;
$(DeployFolder)\FileNotFound.html;
$(DeployFolder)\Global.asax"/>
Step 7 | 复制需要的资源文件
<RemoveDir Directories="$(DeployFolder)\Content\Images\uploaded\" />
<CreateItem Include="$(RootFolder)\$(DeployBranch)\Presentation\Nop.Web\Content\Images\uploaded\**\*.*">
<Output TaskParameter="Include" ItemName="uploadedImages" />
</CreateItem>
<Copy SourceFiles="@(uploadedImages)"
DestinationFolder="$(DeployFolder)\Content\Images\uploaded\%(RecursiveDir)"
SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true" />
Step 8 | 创建 Plugins\Payments.OceanPayment文件夹
我们的站点用到一个叫OceanPayment的插件,这个插件引用到了项目的一些其他类库,生成之后在这个插件的bin目录底下需要包含一些它引用到的项目内其它类库的dll,比如Nop.Web.dll,Nop.Admin.dll,因为dll的加载顺序问题,如果不替换这些dll可能会导致程序发布后,程序用的这些dll仍是旧版本的。曾经在一次发布后发现增加的功能没生效,困扰了我一个下午,后来才发现是因为没替换这个插件里面的这些dll。当然这个步骤不是必要的,如果你的插件没引用到项目其他类库,那就不需要,而且我这边没有去生成其他插件,因为一般不会去修改到插件的代码,假如有动到插件那么再手动发布就好了。
<!-- 创建 Plugins\Payments.OceanPayment文件夹-->
<MakeDir Directories="$(DeployFolder)\Plugins\Payments.OceanPayment" />
<!-- 将根目录bin文件夹里面Nop开头的文件复制一份到Plugins\Payments.OceanPayment -->
<CreateItem Include="$(DeployFolder)\bin\Nop.*">
<Output TaskParameter="Include" ItemName="CompileOutputPlugin" />
</CreateItem>
<Copy SourceFiles="@(CompileOutputPlugin)"
DestinationFolder="$(DeployFolder)\Plugins\Payments.OceanPayment\" />
最后我们在Project
元素里面将其DefaultTargets
设置为Deploy
这个Target
<Project DefaultTargets="Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
</Project>
至此,我们的构建脚本已经基本完成,我们来看一下完整的脚本内容:
<?xml version="1.0" encoding="utf-8" ?>
<Project DefaultTargets="Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<RootFolder>$(MSBuildProjectDirectory)</RootFolder>
<DeployBranch Condition="'$(DeployBranch)' == ''">qa</DeployBranch>
<DeployFolder Condition="'$(DeployFolder)' == ''">$(RootFolder)\Release</DeployFolder>
<WebFolder>$(RootFolder)\$(DeployBranch)\Presentation\Nop.Web</WebFolder>
<Configuration Condition="'$(Configuration)' == ''">Release</Configuration>
<Platform Condition="'$(Platform)' == ''">Any CPU</Platform>
</PropertyGroup>
<Target Name="Deploy">
<!--删除发布文件夹-->
<RemoveDir Directories="$(DeployFolder)" />
<!-- 创建发布文件夹 -->
<MakeDir Directories="$(DeployFolder)" />
<!--开始发布 Nop.Web-->
<MSBuild Projects="$(RootFolder)\$(DeployBranch)\Presentation\Nop.Web\Nop.Web.csproj"
Targets="ResolveReferences;_CopyWebApplication"
Properties="WebProjectOutputDir=$(DeployFolder)\;
OutDir=$(DeployFolder)\bin\;Configuration=$(Configuration)" />
<!--开始发布 Nop.Admin-->
<MSBuild Projects="$(RootFolder)\$(DeployBranch)\Presentation\Nop.Web\Administration\Nop.Admin.csproj"
Targets="ResolveReferences;_CopyWebApplication"
Properties="WebProjectOutputDir=$(DeployFolder)\Administration\;
OutDir=$(DeployFolder)\Administration\bin\;Configuration=$(Configuration)" />
<!--将 Nop.Admin/bin/ 的文件移动到根目录 bin/-->
<CreateItem Include="$(DeployFolder)\Administration\bin\*.dll">
<Output TaskParameter="Include" ItemName="CompileOutput" />
</CreateItem>
<Copy SourceFiles="@(CompileOutput)"
DestinationFolder="$(DeployFolder)\bin\" />
<!--删除Nop.admin里面不必要的文件夹-->
<RemoveDir Directories="$(DeployFolder)\Administration\bin\;
$(DeployFolder)\Administration\App_Data\;
$(DeployFolder)\Administration\Collection\;
$(DeployFolder)\Administration\Scripts\;
$(DeployFolder)\Administration\Themes\;
$(DeployFolder)\App_Data\;" />
<!--删除不必要的文件-->
<Delete Files="$(DeployFolder)\Administration\packages.config;
$(DeployFolder)\packages.config;
$(DeployFolder)\Web.config;
$(DeployFolder)\favicon.ico;
$(DeployFolder)\FileNotFound.html;
$(DeployFolder)\Global.asax"/>
<!--复制 \Contents\Images\uploaded\ directory-->
<RemoveDir Directories="$(DeployFolder)\Content\Images\uploaded\" />
<CreateItem Include="$(RootFolder)\$(DeployBranch)\Presentation\Nop.Web\Content\Images\uploaded\**\*.*">
<Output TaskParameter="Include" ItemName="uploadedImages" />
</CreateItem>
<Copy SourceFiles="@(uploadedImages)"
DestinationFolder="$(DeployFolder)\Content\Images\uploaded\%(RecursiveDir)"
SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true" />
<!-- 创建 Plugins\Payments.OceanPayment文件夹-->
<MakeDir Directories="$(DeployFolder)\Plugins\Payments.OceanPayment" />
<!-- 将根目录bin文件夹里面Nop开头的文件复制一份到Plugins\Payments.OceanPayment -->
<CreateItem Include="$(DeployFolder)\bin\Nop.*">
<Output TaskParameter="Include" ItemName="CompileOutputPlugin" />
</CreateItem>
<Copy SourceFiles="@(CompileOutputPlugin)"
DestinationFolder="$(DeployFolder)\Plugins\Payments.OceanPayment\" />
<!-- 至此构造完成,整个文件夹直接替换到服务器对应的目录 -->
</Target>
</Project>
执行这个构建脚本,最终生成的文件夹结构,然后就可以将这些文件全部替换到服务器的程序目录下了
下一节,Jenkins的配置。
参考: