使用MSBuild实现完整daily build流程

一、MSBuild

在微软软件开发中,每日构建是最重要的过程之一,被称为微软产品开发的“心跳”。简单来看,每天构建系统将整个产品解决方案完整构建一遍,生成的目标文件和安装文件被放置在一个共享位置。接着,安装文件被自动部署到release server上,随后可以自动运行BVT(build verification test),并将所有结果寄送每个team member的信箱。

微软有一套完善的内部系统来完成整个自动化流程,以及流程管理、reporting等工作,而如果我们没有这套系统,也想实现完整的daily build流程,该怎么做呢?

在VS.NET2003时代,IDE可以控制整个方案的构建,但是所有的构建逻辑被IDE控制,对于开发人员来说,整个构建流程就像一个黑箱,很难修改和管理。当然可以使用PreBuildEvent和PostBuildEvent来控制,但是这些event都写在csproj/vbproj文件中,不便于修改,不适于扩展。而且使用IDE来做每日构建的话,要求构建系统本身装有VS.NET,这会带来额外的成本。另一种办法是使用NAnt,通过XML配置文件,来管理构建流程,这会使得整个流程易于修改,可扩展,并且不要求构建系统安装IDE,只需要有framework即可。问题是使用NAnt必须额外写一堆复杂的XML配置文件,足以让很多developer看了头疼。

VS.NET2005中引入了一套全新的构建系统:MSBuild。简单来讲,MSBuild可以直接读取csproj文件,控制csc/vbc等编译器,生成整个方案。实际上,VS2005的IDE自身就是调用MSBuild来完成编译的,这与VS2003有很大的不同。并且由于VS2005的csproj文件服从MSBuild的配置schema,因此我们可以直接使用csproj,稍稍修改一下,就能组织起完整的构建流程了。

二、示例项目的组织

来看一个完整的例子。

我们将建立一个简单的Hello方案,包括一个HelloService(Windows NT Service),一个HelloSite(ASP.NET Web Site),和一个共用组件(class library)。如图所示。

build目录中,将用来存放构建使用的配置文件。private目录中存放解决方案本身。public目录中存放用来完成构建所使用的编译器,例如WiX(用来生成安装包)。先在private目录中新建一个空解决方案,可以命名为“HelloSolution”。然后依次新建SharedComponents、HelloService和HelloSite项目,并建立引用关系:HelloService引用了SharedComponents。最后的文件组织如图所示。

删除Default.aspx第一行的引用,删除Default.aspx.cs,添加一个App_Code目录,在App_Code中新建一个Hello.cs文件,保持初始代码不用修改。

在IDE中编译整个Solution,没问题。可以关闭IDE了。

打开SDK的控制台,输入两条命令:

CD /d C:HelloprivateHelloSolution

msbuild

很快就能看到构建结果了:

但这并不完全是我想要的。例如我想将所有的项目输出都放在C:Hello arget目录;我想让HelloSite有一个自己的主目录;我想自动生成MSI安装包,并且也放在target目录下。

三、修改构建流程

首先我们将SharedComponent.csproj和HelloService.csproj文件copy至build目录下,并将扩展名改名为proj。用记事本打开SharedComponents.proj,看到如下内容。

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

<PropertyGroup>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

<ProductVersion>8.0.50727</ProductVersion>

<SchemaVersion>2.0</SchemaVersion>

<ProjectGuid>{FF79BA82-D6CE-4E89-9AFA-C5EF83A62C2D}</ProjectGuid>

<OutputType>Library</OutputType>

<AppDesignerFolder>Properties</AppDesignerFolder>

<RootNamespace>SharedComponents</RootNamespace>

<AssemblyName>SharedComponents</AssemblyName>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

<DebugSymbols>true</DebugSymbols>

<DebugType>full</DebugType>

<Optimize>false</Optimize>

<OutputPath>binDebug</OutputPath>

<DefineConstants>DEBUG;TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">

<DebugType>pdbonly</DebugType>

<Optimize>true</Optimize>

<OutputPath>binRelease</OutputPath>

<DefineConstants>TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Data" />

<Reference Include="System.Xml" />

</ItemGroup>

<ItemGroup>

<Compile Include="Class1.cs" />

<Compile Include="PropertiesAssemblyInfo.cs" />

</ItemGroup>

<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />

<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

Other similar extension points exist, see Microsoft.Common.targets.

<Target Name="BeforeBuild">

</Target>

<Target Name="AfterBuild">

</Target>

-->

</Project>

观察可以发现,红色的部分是给VS IDE用的,与MSBuild无关,因此可以删除。最后几行中的BeforeBuild和AfterBuild暂时没用,也可以删除。

从第一行开始看:

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

<PropertyGroup>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

首先看到一个属性组,里面的每一条属性都可以理解成一个环境变量。属性组第一行是说,如果环境变量“Configuration”为空,那么设置属性 “Configuration”为“Debug”。同样,第二行是说,如果环境变量“Platform”为空,那么设置属性“Platform”为“AnyCPU”。这里我不想使用“AnyCPU”,于是将其改成“x86”。

<OutputType>Library</OutputType>

<AssemblyName>SharedComponents</AssemblyName>

</PropertyGroup>

OutputType指定了输出类型为类库。AssemblyName指定输出文件名,改为Hello.SharedComponents。

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

<DebugSymbols>true</DebugSymbols>

<DebugType>full</DebugType>

<Optimize>false</Optimize>

<OutputPath>binDebug</OutputPath>

<DefineConstants>DEBUG;TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

在这一段中,如果Configuration属性并上“|” 并上Platform属性,等于“Debug|AnyCPU”的话,那么定义一个属性组。换句话说,就是为debug、AnyCPU的组合配置一段编译器使用的属性。将第一行的Condition改成“'$(Configuration)' == 'Debug'”(假设我们并不需要在其它platform上进行编译)。以同样的方式修改Release的PropertyGroup。

接着是一个ItemGroup,指定了这个项目引用的组件。

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Data" />

<Reference Include="System.Xml" />

</ItemGroup>

然后又是一个ItemGroup,指定了参加编译的源代码文件。

<ItemGroup>

<Compile Include="Class1.cs" />

<Compile Include="PropertiesAssemblyInfo.cs" />

</ItemGroup>

再接下来,引入了一个targets文件:

<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />

$(MSBuildBinPath)是一个环境变量,或者是之前定义的属性。Microsoft.CSharp.targets位于C:WINDOWSMicrosoft.NETFrameworkv2.0.50727目录下,用记事本打开,查找“Name="CoreCompile"”,可以找到真正控制编译器运行的核心配置。其中$(xxx)表示一个之前定义的属性,@(xxx)表示之前定义的ItemGroup。可以发现,先前在SharedComponents.proj中定义的属性和Item,最后实际上都是给这一段CoreCompile用的。由这个target来控制csc编译器执行最终的编译。

在第一个PropertyGroup中添加一个属性:SrcDir,其值为“C:HelloprivateHelloSolutionSharedSharedComponents”,表示此项目源代码文件的位置。相应修改Compile项目组的Include属性为:

<ItemGroup>

<Compile Include="$(SrcDir)Class1.cs" />

<Compile Include="$(SrcDir)PropertiesAssemblyInfo.cs" />

</ItemGroup>

回到HelloService.proj文件,依上所述,进行类似的修改。

注意ProjectReference这个ItemGroup,这一段将会被用来解析依赖关系,需要对Include属性做些修改。

最后形成的两个文件为:

SharedComponents.proj

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

<PropertyGroup>

<SrcDir>C:HelloprivateHelloSolutionSharedSharedComponents</SrcDir>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">x86</Platform>

<OutputType>Library</OutputType>

<AssemblyName>Hello.SharedComponents</AssemblyName>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

<DebugSymbols>true</DebugSymbols>

<DebugType>full</DebugType>

<Optimize>false</Optimize>

<OutputPath>binDebug</OutputPath>

<DefineConstants>DEBUG;TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">

<DebugType>pdbonly</DebugType>

<Optimize>true</Optimize>

<OutputPath>binRelease</OutputPath>

<DefineConstants>TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Data" />

<Reference Include="System.Xml" />

</ItemGroup>

<ItemGroup>

<Compile Include="$(SrcDir)Class1.cs" />

<Compile Include="$(SrcDir)PropertiesAssemblyInfo.cs" />

</ItemGroup>

<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />

</Project>

HelloService.proj

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

<PropertyGroup>

<SrcDir>C:HelloprivateHelloSolutionServicesHelloService</SrcDir>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">x86</Platform>

<OutputType>WinExe</OutputType>

<AssemblyName>Hello.HelloService</AssemblyName>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

<DebugSymbols>true</DebugSymbols>

<DebugType>full</DebugType>

<Optimize>false</Optimize>

<OutputPath>binDebug</OutputPath>

<DefineConstants>DEBUG;TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">

<DebugType>pdbonly</DebugType>

<Optimize>true</Optimize>

<OutputPath>binRelease</OutputPath>

<DefineConstants>TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Data" />

<Reference Include="System.ServiceProcess" />

<Reference Include="System.Xml" />

</ItemGroup>

<ItemGroup>

<Compile Include="$(SrcDir)Service1.cs" />

<Compile Include="$(SrcDir)Service1.Designer.cs" />

<Compile Include="$(SrcDir)Program.cs" />

<Compile Include="$(SrcDir)PropertiesAssemblyInfo.cs" />

</ItemGroup>

<ItemGroup>

<ProjectReference Include="SharedComponents.proj" />

</ItemGroup>

<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />

</Project>

最后参考上面的两个文件和MSDN上MSBuild的资料,新建HelloSite.proj文件:

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

<PropertyGroup>

<VirtualPath>/Hello</VirtualPath>

<PhysicalPath>C:HelloprivateHelloSolutionWebHelloSite</PhysicalPath>

<TargetPath>Web</TargetPath>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

<DebugCompile>true</DebugCompile>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">

<DebugCompile>false</DebugCompile>

</PropertyGroup>

<Target Name="PrecompileWeb">

<AspNetCompiler

VirtualPath="$(VirtualPath)"

PhysicalPath="$(PhysicalPath)"

TargetPath="$(TargetPath)"

Force="true"

Debug="$(DebugCompile)"

Updateable="true" />

</Target>

</Project>

转到控制台,在C:Hellouild目录下执行msbuild HelloService.proj,观察执行结果,发现MSBuild成功解析出HelloService引用了SharedComponent组件,并首先编译了被引用的组件,然后才编译目标组件。如图所示:

再执行msbuild HelloSite.proj,构建也成功了。

四、进一步完善

在这部分中,我们使用环境变量来替代长路径,把项目输出放到指定位置,将公用的属性配置放在一个引用文件里。由于在MSBuild系统中,系统环境变量和属性是通用的,因此这些目标并不难完成。

在C:Hellouild目录中新建一个include.cmd文件。

@echo off

@set public=%inetroot%public

@set private=%inetroot%private

@set target=%inetroot% arget

@set product=%private%HelloSolution

@set setup=%product%Setup

@set wix=%public%WiX

@set Platform=%PROCESSOR_ARCHITECTURE%

call "C:Program FilesMicrosoft Visual Studio 8SDKv2.0Binsdkvars.bat"

在include.cmd中,我们指定了所需的环境变量,并调用了SDK的环境变量设置命令。

新建include.property文件,内容为:

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

<PropertyGroup>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">x86</Platform>

<BaseIntermediateOutputPath>$(target)</BaseIntermediateOutputPath>

<OutputPath>$(target)$(Platform)$(Configuration)</OutputPath>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

<DebugSymbols>true</DebugSymbols>

<DebugType>full</DebugType>

<Optimize>false</Optimize>

<DefineConstants>DEBUG;TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

<DebugCompile>true</DebugCompile>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">

<DebugType>pdbonly</DebugType>

<Optimize>true</Optimize>

<DefineConstants>TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

<DebugCompile>false</DebugCompile>

</PropertyGroup>

</Project>

修改SharedComponents.proj:

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

<Import Project="include.property" />

<PropertyGroup>

<SrcDir>$(product)SharedSharedComponents</SrcDir>

<OutputType>Library</OutputType>

<AssemblyName>Hello.SharedComponents</AssemblyName>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Data" />

<Reference Include="System.Xml" />

</ItemGroup>

<ItemGroup>

<Compile Include="$(SrcDir)Class1.cs" />

<Compile Include="$(SrcDir)PropertiesAssemblyInfo.cs" />

</ItemGroup>

<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />

</Project>

修改HelloService.proj:

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

<Import Project="include.property" />

<PropertyGroup>

<SrcDir>$(product)ServicesHelloService</SrcDir>

<OutputType>WinExe</OutputType>

<AssemblyName>Hello.HelloService</AssemblyName>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Data" />

<Reference Include="System.ServiceProcess" />

<Reference Include="System.Xml" />

</ItemGroup>

<ItemGroup>

<Compile Include="$(SrcDir)Service1.cs" />

<Compile Include="$(SrcDir)Service1.Designer.cs" />

<Compile Include="$(SrcDir)Program.cs" />

<Compile Include="$(SrcDir)PropertiesAssemblyInfo.cs" />

</ItemGroup>

<ItemGroup>

<ProjectReference Include="SharedComponents.proj" />

</ItemGroup>

<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />

</Project>

修改HelloSite.proj:

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

<Import Project="include.property" />

<PropertyGroup>

<VirtualPath>/Hello</VirtualPath>

<PhysicalPath>C:HelloprivateHelloSolutionWebHelloSite</PhysicalPath>

<TargetPath>$( OutputPath)Web</TargetPath>

</PropertyGroup>

<Target Name="PrecompileWeb">

<AspNetCompiler

VirtualPath="$(VirtualPath)"

PhysicalPath="$(PhysicalPath)"

TargetPath="$(TargetPath)"

Force="true"

Debug="$(DebugCompile)"

Updateable="true" />

</Target>

</Project>

在C:Hello目录下新建一个build.proj文件:

<Project DefaultTargets="Compile"

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

<Target Name="Build">

<MSBuild Projects="buildHelloService.proj" />

<MSBuild Projects="buildHelloSite.proj" />

</Target>

</Project>

在C:Hello目录中新建一个build.cmd文件:

@echo off

@set Configuration=Debug

msbuild build.proj /t:Build

@set Configuration=Release

msbuild build.proj /t:Build

在桌面上新建一个快捷方式,命名为“Hello”,Target设置为:

C:WINDOWSsystem32cmd.exe /K set inetroot=C:Hello&"C:HellouildInclude.cmd"

Start in设置为C:Hello,Option中选上“QuickEdit mode”。

退出命令行,双击桌面上的Hello快捷方式,运行build,大约五秒钟后,整个方案就被成功构建了,所有的项目输出都在C:Hello arget目录下。

五、Installer

VS.NET中可以新建一个安装项目,用来编译生成安装包,但是这种生成方式类似用IDE来build项目一样,不适于扩展,而且很难通过命令行来执行编译。

替代的方法是使用WiX toolset。WiX是Windows Installer XML的缩写,是微软的第一个开源项目,可以在SourceForge上下载。

在C:HelloprivateHelloSolution目录下新建一个Setup目录。新建一个HelloService.wxs文件:

<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">

<Product Id="YOURGUID" Language="1033" Manufacturer="Hello Corporation" Name="HelloService" Version="1.0.0.0">

<Package Id="YOURGUID"

Description='Hello Service Windows Installer package'

Manufacturer='Hello Corporation' InstallerVersion='200' Compressed='yes' />

<Condition Message="You need to be an administrator to install this product.">

Privileged

</Condition>

<Condition Message='This product can only be installed on Windows Server 2003'>

VersionNT = 502

</Condition>

<Media Id="1" Cabinet='product.cab' EmbedCab='yes' />

<Directory Id='TARGETDIR' Name='SourceDir'>

<Directory Id='ProgramFilesFolder' Name='PFiles'>

<Directory Id='Hello' Name='Hello'>

<Directory Id='INSTALLDIR' Name='Service'>

<Component Id='MainExecutable' Guid=' YOURGUID '>

<File Id='HelloSvc' Name='Svc.exe' LongName='Hello.HelloService.exe' DiskId='1' src='$(env.target)$(env.Platform)$(env.Configuration)Hello.HelloService.exe' Vital='yes' ProcessorArchitecture="x86" />

<ServiceInstall Id='ServiceInstall' DisplayName='Hello Service' Name='HelloService' ErrorControl='normal' Start='demand' Type='ownProcess' Vital='yes' Description="Hello service" />

<ServiceControl Id="ServiceUninstall" Name="HelloService" Stop="both" Remove="uninstall" Wait="yes" />

</Component>

<Component Id="ReferencedLib" Guid='YOURGUID'>

<File Id='SharedComponents' Name='shared.dll' LongName='Hello.SharedComponents.dll' DiskId='1' src='$(env.target)$(env.Platform)$(env.Configuration)Hello.SharedComponents.dll' Vital='yes' ProcessorArchitecture="x86" />

</Component>

</Directory>

</Directory>

</Directory>

</Directory>

<Feature Id='Complete' Level='1'>

<ComponentRef Id='MainExecutable' />

<ComponentRef Id='ReferencedLib' />

</Feature>

</Product>

</Wix>

其中的YOURGUID需要用一个自己生成的guid来代替,可以用C:Program FilesMicrosoft Visual Studio 8Common7Toolsguidgen.exe来生成guid。

新建HelloSite.wxs文件:

<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">

<Product Id="YOURGUID" Language="1033" Manufacturer="Hello Corporation" Name="HelloSite" Version="1.0.0.0">

<Package Id="YOURGUID"

Description='Hello Site Windows Installer package'

Manufacturer='Hello Corporation' InstallerVersion='200' Compressed='yes' />

<Condition Message="You need to be an administrator to install this product.">

Privileged

</Condition>

<Condition Message='This product can only be installed on Windows Server 2003'>

VersionNT = 502

</Condition>

<Media Id="1" Cabinet='product.cab' EmbedCab='yes' />

<Directory Id='TARGETDIR' Name='SourceDir'>

<Directory Id='ProgramFilesFolder' Name='PFiles'>

<Directory Id='Hello' Name='Hello'>

<Directory Id='INSTALLDIR' Name='Site'>

<Component Id='Page' Guid='YOURGUID'>

<File Id='Default_aspx' Name='Default.asp' LongName='Default.aspx' DiskId='1' src='$(env.target)$(env.Platform)$(env.Configuration)WebDefault.aspx' Vital='yes' />

</Component>

<Directory Id='binDir' Name='bin'>

<Component Id="ReferencedLib" Guid='YOURGUID'>

<File Id='AppCode' Name='App_Code.dll' LongName='App_Code.dll' DiskId='1' src='$(env.target)$(env.Platform)$(env.Configuration)WebinApp_Code.dll' Vital='yes' ProcessorArchitecture="x86" />

</Component>

</Directory>

</Directory>

</Directory>

</Directory>

<Component Id='SiteInstall' Guid='YOURGUID'>

<WebSite Id='DefaultWebSite' Description='Default Web Site' Directory='INSTALLDIR' DirProperties='webSiteProp'>

<WebAddress Id='AllUnassigned' Port='80' />

<WebApplication Id='HelloSite' Name='HelloSite' />

</WebSite>

</Component>

</Directory>

<Feature Id='Complete' Level='1'>

<ComponentRef Id='Page' />

<ComponentRef Id='ReferencedLib' />

<ComponentRef Id='SiteInstall' />

</Feature>

<WebDirProperties Id='webSiteProp' Script='yes' />

<CustomAction Id='ToggleASPNETVersion' ExeCommand='aspnet_regiis -s W3SVC/1/Root/' />

</Product>

</Wix>

注意HelloSite将在把Site程序安装在WebSite下,因此不要在你的关键机器上安装这个示例,并且安装HelloSite前要备份你的IIS设置。

修改build.cmd文件:

@echo off

@set Configuration=Debug

msbuild build.proj /t:Build

if not exist "%target%\%Platform%\%Configuration%Setup" (mkdir "%target%\%Platform%\%Configuration%Setup") ELSE (del "%target%\%Platform%\%Configuration%Setup*.*" /q)

%wix%candle %setup%HelloService.wxs -out "%target%\%Platform%\%Configuration%Setup\"

%wix%light "%target%\%Platform%\%Configuration%SetupHelloService.wixobj" /out "%target%\%Platform%\%Configuration%SetupHelloService.msi"

%wix%candle %setup%HelloSite.wxs -out "%target%\%Platform%\%Configuration%Setup\"

%wix%light "%target%\%Platform%\%Configuration%SetupHelloSite.wixobj" "%wix%casca.wixlib" /out "%target%\%Platform%\%Configuration%SetupHelloSite.msi"

@set Configuration=Release

msbuild build.proj /t:Build

if not exist "%target%\%Platform%\%Configuration%Setup" (mkdir "%target%\%Platform%\%Configuration%Setup") ELSE (del "%target%\%Platform%\%Configuration%Setup*.*" /q)

%wix%candle %setup%HelloService.wxs -out "%target%\%Platform%\%Configuration%Setup\"

%wix%light "%target%\%Platform%\%Configuration%SetupHelloService.wixobj" /out "%target%\%Platform%\%Configuration%SetupHelloService.msi"

%wix%candle %setup%HelloSite.wxs -out "%target%\%Platform%\%Configuration%Setup\"

%wix%light "%target%\%Platform%\%Configuration%SetupHelloSite.wixobj" "%wix%casca.wixlib" /out "%target%\%Platform%\%Configuration%SetupHelloSite.msi"

运行build,构建完成后,发现target目录中,MSI installer也被生成了。接下去运行

msiexec /i %target%\%Platform%\%Configuration%SetupHelloService.msi

C:Program FilesHelloService目录中出现了安装好的文件。打开services.msc,找到“Hello Service”,试着运行一下。

运行msiexec /x %target%\%Platform%\%Configuration%SetupHelloService.msi卸载。

运行msiexec /i %target%\%Platform%\%Configuration%SetupHelloSite.msi安装Web程序。

运行msiexec /x %target%\%Platform%\%Configuration%SetupHelloSite.msi卸载。

六、自动化

最后一个任务就是实现自动化,每日定时构建。

在C:Hello目录中新建一个DailyBuild.bat文件:

@echo off

@set inetroot=C:Hello

call C:HellouildInclude.cmd

call C:Hellouild.cmd

在Services.msc中enable “Task Scheduler”服务。在控制面板的“Scheduled Tasks”中,新建一个任务:Build Hello,指定其每天03:00AM运行C:DailyBuild.bat。

右键点击这个task,选择运行,可以先看一下结果。

就这样,daily build的任务完全实现自动化了。

七、扩展

在示例方案中,由于MSI安装包都已经自动生成了,接下去能做的就更多了,例如实现自动部署,自动测试(BVT),自动report结果,等等。这些工作需要与tester合作,本文不再展开。

自动化流程是保持项目良好运作的关键,在微软公司,这一流程受到高度的重视,通常由developer manager直接负责。如果哪天出现了build break,那么developer在开始一天的coding之前,必须先找到昨天的build哪里出现了问题,先去修复,重新build,直到build成功为止,没有例外。

转自:http://blog.csdn.net/dz45693/article/details/6752653

出处:http://www.cnblogs.com/xgcblog/archive/2011/09/17/2179507.html

一、MSBuild

在微软软件开发中,每日构建是最重要的过程之一,被称为微软产品开发的“心跳”。简单来看,每天构建系统将整个产品解决方案完整构建一遍,生成的目标文件和安装文件被放置在一个共享位置。接着,安装文件被自动部署到release server上,随后可以自动运行BVT(build verification test),并将所有结果寄送每个team member的信箱。

微软有一套完善的内部系统来完成整个自动化流程,以及流程管理、reporting等工作,而如果我们没有这套系统,也想实现完整的daily build流程,该怎么做呢?

在VS.NET2003时代,IDE可以控制整个方案的构建,但是所有的构建逻辑被IDE控制,对于开发人员来说,整个构建流程就像一个黑箱,很难修改和管理。当然可以使用PreBuildEvent和PostBuildEvent来控制,但是这些event都写在csproj/vbproj文件中,不便于修改,不适于扩展。而且使用IDE来做每日构建的话,要求构建系统本身装有VS.NET,这会带来额外的成本。另一种办法是使用NAnt,通过XML配置文件,来管理构建流程,这会使得整个流程易于修改,可扩展,并且不要求构建系统安装IDE,只需要有framework即可。问题是使用NAnt必须额外写一堆复杂的XML配置文件,足以让很多developer看了头疼。

VS.NET2005中引入了一套全新的构建系统:MSBuild。简单来讲,MSBuild可以直接读取csproj文件,控制csc/vbc等编译器,生成整个方案。实际上,VS2005的IDE自身就是调用MSBuild来完成编译的,这与VS2003有很大的不同。并且由于VS2005的csproj文件服从MSBuild的配置schema,因此我们可以直接使用csproj,稍稍修改一下,就能组织起完整的构建流程了。

二、示例项目的组织

来看一个完整的例子。

我们将建立一个简单的Hello方案,包括一个HelloService(Windows NT Service),一个HelloSite(ASP.NET Web Site),和一个共用组件(class library)。如图所示。

build目录中,将用来存放构建使用的配置文件。private目录中存放解决方案本身。public目录中存放用来完成构建所使用的编译器,例如WiX(用来生成安装包)。先在private目录中新建一个空解决方案,可以命名为“HelloSolution”。然后依次新建SharedComponents、HelloService和HelloSite项目,并建立引用关系:HelloService引用了SharedComponents。最后的文件组织如图所示。

删除Default.aspx第一行的引用,删除Default.aspx.cs,添加一个App_Code目录,在App_Code中新建一个Hello.cs文件,保持初始代码不用修改。

在IDE中编译整个Solution,没问题。可以关闭IDE了。

打开SDK的控制台,输入两条命令:

CD /d C:HelloprivateHelloSolution

msbuild

很快就能看到构建结果了:

但这并不完全是我想要的。例如我想将所有的项目输出都放在C:Hello arget目录;我想让HelloSite有一个自己的主目录;我想自动生成MSI安装包,并且也放在target目录下。

三、修改构建流程

首先我们将SharedComponent.csproj和HelloService.csproj文件copy至build目录下,并将扩展名改名为proj。用记事本打开SharedComponents.proj,看到如下内容。

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

<PropertyGroup>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

<ProductVersion>8.0.50727</ProductVersion>

<SchemaVersion>2.0</SchemaVersion>

<ProjectGuid>{FF79BA82-D6CE-4E89-9AFA-C5EF83A62C2D}</ProjectGuid>

<OutputType>Library</OutputType>

<AppDesignerFolder>Properties</AppDesignerFolder>

<RootNamespace>SharedComponents</RootNamespace>

<AssemblyName>SharedComponents</AssemblyName>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

<DebugSymbols>true</DebugSymbols>

<DebugType>full</DebugType>

<Optimize>false</Optimize>

<OutputPath>binDebug</OutputPath>

<DefineConstants>DEBUG;TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">

<DebugType>pdbonly</DebugType>

<Optimize>true</Optimize>

<OutputPath>binRelease</OutputPath>

<DefineConstants>TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Data" />

<Reference Include="System.Xml" />

</ItemGroup>

<ItemGroup>

<Compile Include="Class1.cs" />

<Compile Include="PropertiesAssemblyInfo.cs" />

</ItemGroup>

<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />

<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

Other similar extension points exist, see Microsoft.Common.targets.

<Target Name="BeforeBuild">

</Target>

<Target Name="AfterBuild">

</Target>

-->

</Project>

观察可以发现,红色的部分是给VS IDE用的,与MSBuild无关,因此可以删除。最后几行中的BeforeBuild和AfterBuild暂时没用,也可以删除。

从第一行开始看:

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

<PropertyGroup>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

首先看到一个属性组,里面的每一条属性都可以理解成一个环境变量。属性组第一行是说,如果环境变量“Configuration”为空,那么设置属性 “Configuration”为“Debug”。同样,第二行是说,如果环境变量“Platform”为空,那么设置属性“Platform”为“AnyCPU”。这里我不想使用“AnyCPU”,于是将其改成“x86”。

<OutputType>Library</OutputType>

<AssemblyName>SharedComponents</AssemblyName>

</PropertyGroup>

OutputType指定了输出类型为类库。AssemblyName指定输出文件名,改为Hello.SharedComponents。

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

<DebugSymbols>true</DebugSymbols>

<DebugType>full</DebugType>

<Optimize>false</Optimize>

<OutputPath>binDebug</OutputPath>

<DefineConstants>DEBUG;TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

在这一段中,如果Configuration属性并上“|” 并上Platform属性,等于“Debug|AnyCPU”的话,那么定义一个属性组。换句话说,就是为debug、AnyCPU的组合配置一段编译器使用的属性。将第一行的Condition改成“'$(Configuration)' == 'Debug'”(假设我们并不需要在其它platform上进行编译)。以同样的方式修改Release的PropertyGroup。

接着是一个ItemGroup,指定了这个项目引用的组件。

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Data" />

<Reference Include="System.Xml" />

</ItemGroup>

然后又是一个ItemGroup,指定了参加编译的源代码文件。

<ItemGroup>

<Compile Include="Class1.cs" />

<Compile Include="PropertiesAssemblyInfo.cs" />

</ItemGroup>

再接下来,引入了一个targets文件:

<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />

$(MSBuildBinPath)是一个环境变量,或者是之前定义的属性。Microsoft.CSharp.targets位于C:WINDOWSMicrosoft.NETFrameworkv2.0.50727目录下,用记事本打开,查找“Name="CoreCompile"”,可以找到真正控制编译器运行的核心配置。其中$(xxx)表示一个之前定义的属性,@(xxx)表示之前定义的ItemGroup。可以发现,先前在SharedComponents.proj中定义的属性和Item,最后实际上都是给这一段CoreCompile用的。由这个target来控制csc编译器执行最终的编译。

在第一个PropertyGroup中添加一个属性:SrcDir,其值为“C:HelloprivateHelloSolutionSharedSharedComponents”,表示此项目源代码文件的位置。相应修改Compile项目组的Include属性为:

<ItemGroup>

<Compile Include="$(SrcDir)Class1.cs" />

<Compile Include="$(SrcDir)PropertiesAssemblyInfo.cs" />

</ItemGroup>

回到HelloService.proj文件,依上所述,进行类似的修改。

注意ProjectReference这个ItemGroup,这一段将会被用来解析依赖关系,需要对Include属性做些修改。

最后形成的两个文件为:

SharedComponents.proj

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

<PropertyGroup>

<SrcDir>C:HelloprivateHelloSolutionSharedSharedComponents</SrcDir>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">x86</Platform>

<OutputType>Library</OutputType>

<AssemblyName>Hello.SharedComponents</AssemblyName>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

<DebugSymbols>true</DebugSymbols>

<DebugType>full</DebugType>

<Optimize>false</Optimize>

<OutputPath>binDebug</OutputPath>

<DefineConstants>DEBUG;TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">

<DebugType>pdbonly</DebugType>

<Optimize>true</Optimize>

<OutputPath>binRelease</OutputPath>

<DefineConstants>TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Data" />

<Reference Include="System.Xml" />

</ItemGroup>

<ItemGroup>

<Compile Include="$(SrcDir)Class1.cs" />

<Compile Include="$(SrcDir)PropertiesAssemblyInfo.cs" />

</ItemGroup>

<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />

</Project>

HelloService.proj

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

<PropertyGroup>

<SrcDir>C:HelloprivateHelloSolutionServicesHelloService</SrcDir>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">x86</Platform>

<OutputType>WinExe</OutputType>

<AssemblyName>Hello.HelloService</AssemblyName>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

<DebugSymbols>true</DebugSymbols>

<DebugType>full</DebugType>

<Optimize>false</Optimize>

<OutputPath>binDebug</OutputPath>

<DefineConstants>DEBUG;TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">

<DebugType>pdbonly</DebugType>

<Optimize>true</Optimize>

<OutputPath>binRelease</OutputPath>

<DefineConstants>TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Data" />

<Reference Include="System.ServiceProcess" />

<Reference Include="System.Xml" />

</ItemGroup>

<ItemGroup>

<Compile Include="$(SrcDir)Service1.cs" />

<Compile Include="$(SrcDir)Service1.Designer.cs" />

<Compile Include="$(SrcDir)Program.cs" />

<Compile Include="$(SrcDir)PropertiesAssemblyInfo.cs" />

</ItemGroup>

<ItemGroup>

<ProjectReference Include="SharedComponents.proj" />

</ItemGroup>

<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />

</Project>

最后参考上面的两个文件和MSDN上MSBuild的资料,新建HelloSite.proj文件:

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

<PropertyGroup>

<VirtualPath>/Hello</VirtualPath>

<PhysicalPath>C:HelloprivateHelloSolutionWebHelloSite</PhysicalPath>

<TargetPath>Web</TargetPath>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

<DebugCompile>true</DebugCompile>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">

<DebugCompile>false</DebugCompile>

</PropertyGroup>

<Target Name="PrecompileWeb">

<AspNetCompiler

VirtualPath="$(VirtualPath)"

PhysicalPath="$(PhysicalPath)"

TargetPath="$(TargetPath)"

Force="true"

Debug="$(DebugCompile)"

Updateable="true" />

</Target>

</Project>

转到控制台,在C:Hellouild目录下执行msbuild HelloService.proj,观察执行结果,发现MSBuild成功解析出HelloService引用了SharedComponent组件,并首先编译了被引用的组件,然后才编译目标组件。如图所示:

再执行msbuild HelloSite.proj,构建也成功了。

四、进一步完善

在这部分中,我们使用环境变量来替代长路径,把项目输出放到指定位置,将公用的属性配置放在一个引用文件里。由于在MSBuild系统中,系统环境变量和属性是通用的,因此这些目标并不难完成。

在C:Hellouild目录中新建一个include.cmd文件。

@echo off

@set public=%inetroot%public

@set private=%inetroot%private

@set target=%inetroot% arget

@set product=%private%HelloSolution

@set setup=%product%Setup

@set wix=%public%WiX

@set Platform=%PROCESSOR_ARCHITECTURE%

call "C:Program FilesMicrosoft Visual Studio 8SDKv2.0Binsdkvars.bat"

在include.cmd中,我们指定了所需的环境变量,并调用了SDK的环境变量设置命令。

新建include.property文件,内容为:

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

<PropertyGroup>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">x86</Platform>

<BaseIntermediateOutputPath>$(target)</BaseIntermediateOutputPath>

<OutputPath>$(target)$(Platform)$(Configuration)</OutputPath>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

<DebugSymbols>true</DebugSymbols>

<DebugType>full</DebugType>

<Optimize>false</Optimize>

<DefineConstants>DEBUG;TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

<DebugCompile>true</DebugCompile>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">

<DebugType>pdbonly</DebugType>

<Optimize>true</Optimize>

<DefineConstants>TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

<DebugCompile>false</DebugCompile>

</PropertyGroup>

</Project>

修改SharedComponents.proj:

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

<Import Project="include.property" />

<PropertyGroup>

<SrcDir>$(product)SharedSharedComponents</SrcDir>

<OutputType>Library</OutputType>

<AssemblyName>Hello.SharedComponents</AssemblyName>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Data" />

<Reference Include="System.Xml" />

</ItemGroup>

<ItemGroup>

<Compile Include="$(SrcDir)Class1.cs" />

<Compile Include="$(SrcDir)PropertiesAssemblyInfo.cs" />

</ItemGroup>

<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />

</Project>

修改HelloService.proj:

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

<Import Project="include.property" />

<PropertyGroup>

<SrcDir>$(product)ServicesHelloService</SrcDir>

<OutputType>WinExe</OutputType>

<AssemblyName>Hello.HelloService</AssemblyName>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Data" />

<Reference Include="System.ServiceProcess" />

<Reference Include="System.Xml" />

</ItemGroup>

<ItemGroup>

<Compile Include="$(SrcDir)Service1.cs" />

<Compile Include="$(SrcDir)Service1.Designer.cs" />

<Compile Include="$(SrcDir)Program.cs" />

<Compile Include="$(SrcDir)PropertiesAssemblyInfo.cs" />

</ItemGroup>

<ItemGroup>

<ProjectReference Include="SharedComponents.proj" />

</ItemGroup>

<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />

</Project>

修改HelloSite.proj:

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

<Import Project="include.property" />

<PropertyGroup>

<VirtualPath>/Hello</VirtualPath>

<PhysicalPath>C:HelloprivateHelloSolutionWebHelloSite</PhysicalPath>

<TargetPath>$( OutputPath)Web</TargetPath>

</PropertyGroup>

<Target Name="PrecompileWeb">

<AspNetCompiler

VirtualPath="$(VirtualPath)"

PhysicalPath="$(PhysicalPath)"

TargetPath="$(TargetPath)"

Force="true"

Debug="$(DebugCompile)"

Updateable="true" />

</Target>

</Project>

在C:Hello目录下新建一个build.proj文件:

<Project DefaultTargets="Compile"

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

<Target Name="Build">

<MSBuild Projects="buildHelloService.proj" />

<MSBuild Projects="buildHelloSite.proj" />

</Target>

</Project>

在C:Hello目录中新建一个build.cmd文件:

@echo off

@set Configuration=Debug

msbuild build.proj /t:Build

@set Configuration=Release

msbuild build.proj /t:Build

在桌面上新建一个快捷方式,命名为“Hello”,Target设置为:

C:WINDOWSsystem32cmd.exe /K set inetroot=C:Hello&"C:HellouildInclude.cmd"

Start in设置为C:Hello,Option中选上“QuickEdit mode”。

退出命令行,双击桌面上的Hello快捷方式,运行build,大约五秒钟后,整个方案就被成功构建了,所有的项目输出都在C:Hello arget目录下。

五、Installer

VS.NET中可以新建一个安装项目,用来编译生成安装包,但是这种生成方式类似用IDE来build项目一样,不适于扩展,而且很难通过命令行来执行编译。

替代的方法是使用WiX toolset。WiX是Windows Installer XML的缩写,是微软的第一个开源项目,可以在SourceForge上下载。

在C:HelloprivateHelloSolution目录下新建一个Setup目录。新建一个HelloService.wxs文件:

<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">

<Product Id="YOURGUID" Language="1033" Manufacturer="Hello Corporation" Name="HelloService" Version="1.0.0.0">

<Package Id="YOURGUID"

Description='Hello Service Windows Installer package'

Manufacturer='Hello Corporation' InstallerVersion='200' Compressed='yes' />

<Condition Message="You need to be an administrator to install this product.">

Privileged

</Condition>

<Condition Message='This product can only be installed on Windows Server 2003'>

VersionNT = 502

</Condition>

<Media Id="1" Cabinet='product.cab' EmbedCab='yes' />

<Directory Id='TARGETDIR' Name='SourceDir'>

<Directory Id='ProgramFilesFolder' Name='PFiles'>

<Directory Id='Hello' Name='Hello'>

<Directory Id='INSTALLDIR' Name='Service'>

<Component Id='MainExecutable' Guid=' YOURGUID '>

<File Id='HelloSvc' Name='Svc.exe' LongName='Hello.HelloService.exe' DiskId='1' src='$(env.target)$(env.Platform)$(env.Configuration)Hello.HelloService.exe' Vital='yes' ProcessorArchitecture="x86" />

<ServiceInstall Id='ServiceInstall' DisplayName='Hello Service' Name='HelloService' ErrorControl='normal' Start='demand' Type='ownProcess' Vital='yes' Description="Hello service" />

<ServiceControl Id="ServiceUninstall" Name="HelloService" Stop="both" Remove="uninstall" Wait="yes" />

</Component>

<Component Id="ReferencedLib" Guid='YOURGUID'>

<File Id='SharedComponents' Name='shared.dll' LongName='Hello.SharedComponents.dll' DiskId='1' src='$(env.target)$(env.Platform)$(env.Configuration)Hello.SharedComponents.dll' Vital='yes' ProcessorArchitecture="x86" />

</Component>

</Directory>

</Directory>

</Directory>

</Directory>

<Feature Id='Complete' Level='1'>

<ComponentRef Id='MainExecutable' />

<ComponentRef Id='ReferencedLib' />

</Feature>

</Product>

</Wix>

其中的YOURGUID需要用一个自己生成的guid来代替,可以用C:Program FilesMicrosoft Visual Studio 8Common7Toolsguidgen.exe来生成guid。

新建HelloSite.wxs文件:

<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">

<Product Id="YOURGUID" Language="1033" Manufacturer="Hello Corporation" Name="HelloSite" Version="1.0.0.0">

<Package Id="YOURGUID"

Description='Hello Site Windows Installer package'

Manufacturer='Hello Corporation' InstallerVersion='200' Compressed='yes' />

<Condition Message="You need to be an administrator to install this product.">

Privileged

</Condition>

<Condition Message='This product can only be installed on Windows Server 2003'>

VersionNT = 502

</Condition>

<Media Id="1" Cabinet='product.cab' EmbedCab='yes' />

<Directory Id='TARGETDIR' Name='SourceDir'>

<Directory Id='ProgramFilesFolder' Name='PFiles'>

<Directory Id='Hello' Name='Hello'>

<Directory Id='INSTALLDIR' Name='Site'>

<Component Id='Page' Guid='YOURGUID'>

<File Id='Default_aspx' Name='Default.asp' LongName='Default.aspx' DiskId='1' src='$(env.target)$(env.Platform)$(env.Configuration)WebDefault.aspx' Vital='yes' />

</Component>

<Directory Id='binDir' Name='bin'>

<Component Id="ReferencedLib" Guid='YOURGUID'>

<File Id='AppCode' Name='App_Code.dll' LongName='App_Code.dll' DiskId='1' src='$(env.target)$(env.Platform)$(env.Configuration)WebinApp_Code.dll' Vital='yes' ProcessorArchitecture="x86" />

</Component>

</Directory>

</Directory>

</Directory>

</Directory>

<Component Id='SiteInstall' Guid='YOURGUID'>

<WebSite Id='DefaultWebSite' Description='Default Web Site' Directory='INSTALLDIR' DirProperties='webSiteProp'>

<WebAddress Id='AllUnassigned' Port='80' />

<WebApplication Id='HelloSite' Name='HelloSite' />

</WebSite>

</Component>

</Directory>

<Feature Id='Complete' Level='1'>

<ComponentRef Id='Page' />

<ComponentRef Id='ReferencedLib' />

<ComponentRef Id='SiteInstall' />

</Feature>

<WebDirProperties Id='webSiteProp' Script='yes' />

<CustomAction Id='ToggleASPNETVersion' ExeCommand='aspnet_regiis -s W3SVC/1/Root/' />

</Product>

</Wix>

注意HelloSite将在把Site程序安装在WebSite下,因此不要在你的关键机器上安装这个示例,并且安装HelloSite前要备份你的IIS设置。

修改build.cmd文件:

@echo off

@set Configuration=Debug

msbuild build.proj /t:Build

if not exist "%target%\%Platform%\%Configuration%Setup" (mkdir "%target%\%Platform%\%Configuration%Setup") ELSE (del "%target%\%Platform%\%Configuration%Setup*.*" /q)

%wix%candle %setup%HelloService.wxs -out "%target%\%Platform%\%Configuration%Setup\"

%wix%light "%target%\%Platform%\%Configuration%SetupHelloService.wixobj" /out "%target%\%Platform%\%Configuration%SetupHelloService.msi"

%wix%candle %setup%HelloSite.wxs -out "%target%\%Platform%\%Configuration%Setup\"

%wix%light "%target%\%Platform%\%Configuration%SetupHelloSite.wixobj" "%wix%casca.wixlib" /out "%target%\%Platform%\%Configuration%SetupHelloSite.msi"

@set Configuration=Release

msbuild build.proj /t:Build

if not exist "%target%\%Platform%\%Configuration%Setup" (mkdir "%target%\%Platform%\%Configuration%Setup") ELSE (del "%target%\%Platform%\%Configuration%Setup*.*" /q)

%wix%candle %setup%HelloService.wxs -out "%target%\%Platform%\%Configuration%Setup\"

%wix%light "%target%\%Platform%\%Configuration%SetupHelloService.wixobj" /out "%target%\%Platform%\%Configuration%SetupHelloService.msi"

%wix%candle %setup%HelloSite.wxs -out "%target%\%Platform%\%Configuration%Setup\"

%wix%light "%target%\%Platform%\%Configuration%SetupHelloSite.wixobj" "%wix%casca.wixlib" /out "%target%\%Platform%\%Configuration%SetupHelloSite.msi"

运行build,构建完成后,发现target目录中,MSI installer也被生成了。接下去运行

msiexec /i %target%\%Platform%\%Configuration%SetupHelloService.msi

C:Program FilesHelloService目录中出现了安装好的文件。打开services.msc,找到“Hello Service”,试着运行一下。

运行msiexec /x %target%\%Platform%\%Configuration%SetupHelloService.msi卸载。

运行msiexec /i %target%\%Platform%\%Configuration%SetupHelloSite.msi安装Web程序。

运行msiexec /x %target%\%Platform%\%Configuration%SetupHelloSite.msi卸载。

六、自动化

最后一个任务就是实现自动化,每日定时构建。

在C:Hello目录中新建一个DailyBuild.bat文件:

@echo off

@set inetroot=C:Hello

call C:HellouildInclude.cmd

call C:Hellouild.cmd

在Services.msc中enable “Task Scheduler”服务。在控制面板的“Scheduled Tasks”中,新建一个任务:Build Hello,指定其每天03:00AM运行C:DailyBuild.bat。

右键点击这个task,选择运行,可以先看一下结果。

就这样,daily build的任务完全实现自动化了。

七、扩展

在示例方案中,由于MSI安装包都已经自动生成了,接下去能做的就更多了,例如实现自动部署,自动测试(BVT),自动report结果,等等。这些工作需要与tester合作,本文不再展开。

自动化流程是保持项目良好运作的关键,在微软公司,这一流程受到高度的重视,通常由developer manager直接负责。如果哪天出现了build break,那么developer在开始一天的coding之前,必须先找到昨天的build哪里出现了问题,先去修复,重新build,直到build成功为止,没有例外。

转自:http://blog.csdn.net/dz45693/article/details/6752653