本文是從我的 github 博客 http://lxconan.github.io 導入的。
這是這個系列的第四篇了,前三篇請參見:
這一篇中我們會寫一些關于自動化部署的代碼。我們會使用 Powershell 書寫這類代碼。我不會向你講為什么要自動化部署,這種大道理講的太多了。你將發現這篇文章中涉及的東西非常具體,有的要求甚至相當苛刻且可能不具有通用性。這是因為部署從來都是跟環境打交道,部署過程中協作的組建太多,相互之間的交集不可能太大。可能唯一能夠通用的是自動化部署的基本原則(只是這篇文章的基本原則):
- 每一次自動化部署結束之后,應用程序都會有相同的初始狀態。
- 自動化部署的機器非常干凈,只有相應的 Windows Server 系統和 .NET Framework。尤其是,不會有 Visual Studio。
我們需要公開一些基本的環境信息:
- 64-bit Windows Server 2008/2012/2012 或者 64-bit Windows 7/8/8.1
- 我們的工程是使用 Microsoft Visual Studio 2013 開發的;
- .NET Framework 版本為 4.5
- ASP.NET MVC 的版本為 5.0.0
- Microsoft Build Tool 的版本為 12.0
關于 MSBuild 這里需要多說幾句。在 Visual Studio 2013 發布之前,MSBuild 是隨 .NET Framework 一起發布的。這意味著我們可以不用安裝 Visual Studio 就可以構建 .NET 應用程序。
這是多么美好的世界啊!因此,上一句話是假的。尤其是當你構建 ASP.NET Web 應用程序的時候,你馬上就會遇到 "Cannot found ... WebApplication.target" 的錯誤。如果你 StackOverflow 一下就可以知道,我們需要做的是安裝 Visual Studio 或者從另外一臺安裝了 Visual Studio 的機器上將相關的文件拷貝出來。這真是——丟人的設計!Linux 的擁躉又有了發泄的空間。
于是微軟決定正視這個問題。從 Visual Studio 2013 開始,MSBuild 將與 Visual Studio 而不是 .NET Framework 一起發布。這引來了一片罵聲。我不得不說,大家在生活中不論遇到什么事情都要保持足夠的冷靜,往往先發脾氣的得不到任何的好處。事實是,由于 .NET Framework 發布的周期是相對較長的,因此微軟目前越來越傾向于使用 NuGet 進行類庫的發布,這樣有助于削減 .NET Framework 基礎類庫的大小,并縮短基礎類庫的發布周期。相應的 MSBuild 和 Visual Studio 的發布周期也希望進行獨立的變化。MSBuild 將和 Visual Studio 保持發布周期的一致性,并不意味這 MSBuild 依賴于 Visual Studio。因此我們當然可以下載 MSBuild 的獨立安裝包 Microsoft Build Tools,并在一臺沒有 Visual Studio 的服務器上進行自動化構建。
再次強調,如果幸運,你可以直接運行這篇文章中的例子。但是你不要指望將這篇文章的腳本拷貝到你的工程就可以正常工作。因為部署是一個因地制宜的任務,需要具體分析。你可能會遇到各種各樣的環境問題,但是我認為最重要的還是思路。
部署是什么
簡單來說,部署就是 “構建(Build)” -> “拷貝” -> “配置”。那么我們就開始一個一個解決它。為了避免這篇文章過長,我們將分及部分介紹部署的過程。這一篇將著眼于構建。
編譯和構建應用程序 - 思路
如何構建應用程序呢?答案是先把需要的東西都拷貝過來,然后再用工具構建。好極了,我們需要拷貝什么東西呢?當然是先把源代碼拷貝過來。
┌────────────────┐
│ Src of MyApp │
└────────────────┘
光源代碼還不行,因為我們的程序依賴于第三方庫(箭頭代表依賴關系)。
┌────────────────┐
│ Dependent libs │
└────────────────┘
↑
┌────────────────┐
│ Src of MyApp │
└────────────────┘
例如,對于我們構建的那個簡簡單單的 ASP.NET MVC 5 的應用程序,我們就需要在構建之前下載它依賴的類庫:
- Microsoft.AspNet.Mvc 5.0.0
- Microsoft.AspNet.Razor 3.0.0
- Microsoft.AspNet.WebPages 3.0.0
- Microsoft.Web.Infrastructure 1.0.0.0
源代碼和依賴庫都拷貝完了,那么接下來我們就需要將構建工具也拷貝過來:
┌────────────────┐
│ Dependent libs │
└────────────────┘
↑
┌────────────────┐ ┌───────────────────┐
│ Src of MyApp │────→│Tools for compiling│
└────────────────┘ └───────────────────┘
對于我們來說,構建的工具只有一個:
- MSBuild
如今工具,源代碼一應俱全,我們可以開始編譯了。
安裝 MSBuild
如果你做實驗的機器上安裝了 Visual Studio 2013——Express 版本的也是可以的——那么你可以跳過這一步,否則請下載 Microsoft Build Tools 并安裝。
下載應用程序的代碼
我們的代碼是現成的,只需要從 git repository clone 下來就可以了。你也可以從這個地址 clone 到一個范例代碼,并轉換到相應的 commit。
git clone https://github.com/lxconan/MvcFromZero.git
git reset --hard 340abb32433c99975bd6485f79db6ca077119477
好了,完成了,到目前為止一切都是那么的輕松愉快。
下載應用的依賴庫
我們將使用 nuget 管理包的依賴。因此我們首先要下載 NuGet 的命令行客戶端,可以從這里下載。
接下來的行為都需要明確的目錄結構。為了便于說明,我們將建立如下的目錄結構。
MvcFromZero
|- src
| |- FromZero.App
| |- packages
|
|- build
|- tools
其中,src 目錄存放 Solution 的源代碼,而 build 目錄存放自動化構建所需的各種工具和腳本。其中自動化構建需要的工具將放在 build/tools 目錄下。因此我們也會將 nuget.exe 放在這個目錄下。
接下來我們在 build 目錄下建立 deploy.ps1 腳本,我們將在這個腳本中完成接下來的任務。我們先來下載依賴的包。NuGet 的包管理是通過 package.config 文件進行的。需要指出的是 package.config 也具有一定的層次關系。首先 Solution 級別的包信息存儲在兩個地方:
- 第一個是 Solution 目錄下的 .nuget/package.config 文件,這個文件中存儲的是非特定項目使用的包(如果并沒有這種類型的包,則該目錄不存在,或者該目錄下沒有任何文件);
- 第二個是 Solution 目錄下的 packages/repositories.config 文件,這個文件存儲了該解決方案下的每一個工程的 packages.config 文件的路徑。
而 Solution 下的每一個 Project 的包定義存儲在 Project 目錄下的 package.config 中。
為了下載這些依賴的包是否需要人為遍歷這些 config 文件呢?原來是,而從 nuget 2.7 開始,可以直接支持 Solution 范圍內的包下載,于是額我們就可以使用如下的簡單函數完成包的下載了。
Function Install-SolutionPackages() {
iex "$global_nugetPath restore $global_solutionFilePath"
}
其中,$global_nugetPath
是 nuget.exe 的路徑,而 $global_solutionFilePath
是工程文件(在這里是 FromZero.App.csproj)的路徑。
接下來需要確定的是構建工具的位置,我們可以使用注冊表查找 MSBuild 的位置的,由于我們使用了 2013 版本的 MSBuild(Toolset 的版本號為 12.0),因此我們的查找腳本為:
Function Get-MsBuildPath() {
$msBuildRegPath = "HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions\12.0"
$msBuildPathRegItem = Get-ItemProperty $msBuildRegPath -Name "MSBuildToolsPath"
$msBuildPath = $msBuildPathRegItem.MsBuildToolsPath + "msbuild.exe"
return $msBuildPath
}
上述兩步完成之后,就可以編譯我們的工程了:
Function Compile-Project() {
iex -Command "& '$global_msBuildPath' $project_path"
}
你迫不及待的試驗了你的代碼,但是卻發現出了問題,MSBuild 報告無法找到 Microsoft.WebApplication.targets 文件。這是怎么回事,我們已經明明在 MSBuild 中安裝了 Microsoft.WebApplication.targets 了啊?這是因為為了保持和 Microsoft Visual Studio 2010 SP1 的工程的兼容性,Visual Studio 2012/2013 的工程默認將 $(VisualStudioVersion)
環境變量設置為 10.0
。這樣,target 文件的查詢路徑就成了:
$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0
而不是:
$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0
因此我們需要修正這個環境變量。請使用文本編輯器打開 FromZero.App.csproj 文件,找到如下的定義:
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath
Condition="'$(VSToolsPath)' == ''">
$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
</VSToolsPath>
</PropertyGroup>
將其中的 VisualStudioVersion
節替換為:
<VisualStudioVersion>12.0</VisualStudioVersion>
再次運行:祝賀你,你已經能夠成功的下載所有的依賴,并完成工程的編譯了!在本文的結束,附上 deploy.ps1 到目前為止的所有代碼:
$ErrorActionPreference = 'Stop'
# Environment helpers ------------------------------------
Function Get-MsBuildPath() {
$msBuildRegPath = "HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions\12.0"
$msBuildPathRegItem = Get-ItemProperty $msBuildRegPath -Name "MSBuildToolsPath"
$msBuildPath = $msBuildPathRegItem.MsBuildToolsPath + "msbuild.exe"
return $msBuildPath
}
# Environment variables ----------------------------------
$global_buildDirPath = Get-Location
$global_msBuildPath = Get-MsBuildPath
$global_solutionPath = "$global_buildDirPath\..\src"
$global_solutionFilePath = "$global_solutionPath\src.sln"
$global_nugetPath = "$global_buildDirPath\tools\nuget.exe"
# Install nuget packages ---------------------------------
Function Install-SolutionPackages() {
iex "$global_nugetPath restore $global_solutionFilePath"
}
$project_path = $global_solutionPath + '\FromZero.App\FromZero.App.csproj'
Function Compile-Project() {
iex -Command "& '$global_msBuildPath' '$project_path'"
}
Install-SolutionPackages
Compile-Project
文章列表