Jenkins持續部署-自動生成版本號

傑哥很忙發表於2019-06-29

Jenkins持續部署-自動生成版本號


目錄

Jenkins持續整合學習-Windows環境進行.Net開發1
Jenkins持續整合學習-Windows環境進行.Net開發2
Jenkins持續整合學習-Windows環境進行.Net開發3
Jenkins持續整合學習-Windows環境進行.Net開發4
Jenkins持續整合學習-搭建jenkins問題彙總
Jenkins持續部署-Windows環境持續部署探究1
Jenkins持續部署-自動生成版本號

前言

在上一篇之前的文章開始對Windows環境下持續部署的方案進行學習與研究。上一篇文章主要介紹關於持續部署需要的一些技術方案的實現,在本篇文章開始對持續部署的一些細節實現展開討論。
本篇文章先對版本號的自動更新流程進行梳理和說明。後續需要通過版本號比較建立差量更新包。

目的

本章文章主要是通過呼叫svn客戶端命令和powershell指令碼實現完全無需人工干預自動生成版本號。

詳細流程

若程式需要定義版本號,則可以將版本號記錄在程式集的AssemblyInfo.cs檔案中

[assembly: AssemblyVersion("1.0.0")]
[assembly: AssemblyFileVersion("1.0.0")]

也可以在程式集右鍵選擇屬性(或者通過快捷鍵Alt + Enter),在Application點選Assembly Infomation...按鈕修改程式集版本號和檔案版本號。

20190629173644.png

AssemblyVersion是程式集的版本,.NET的CLR用於標識出該dll的版本資訊,用於定義強名稱的版本號,該版本號每一位最大為16位長度,即最大為65535,超過時編譯不通過。
AssemblyFileVersion是檔案版本號,僅僅是檔案版本號,給人看的,沒有實際什麼作用,也沒有長度限制。

獲取SVN Reversion

我們規定程式的版本號為需求版本號1.0.0加上SVN的Reversion做為修訂號。這樣就能直接關聯上該程式集是哪個版本的程式碼。
關於修訂號,在《TortoiseSVN》文件中有相關的說明。我看的是《TortoiseSVN 1.8.10》的文件,在第五章介紹了SubWCRev程式。通過SubWCRev程式可以執行關鍵字$WCREV$替換。同時我們需要提供一個版本號模板檔案,通過替換版本號模板檔案的關鍵字生成我們需要的版本號檔案。

首先我們根據程式集下AssemblyInfo.cs檔案複製出一個AssemblyInfo.template.cs檔案。

由於我們僅僅是為了修改版本號資訊,後面就稱之為版本號模板檔案

然後將其[assembly: AssemblyFileVersion("1.0.0.0")]修改為[assembly: AssemblyFileVersion("1.0.0.$WCREV$")]。這樣我們就可以通過SubWCRev程式替換修訂號。

20190629221330.png

由於AssemblyVersion有大小限制,不允許超過65535,而SVN修訂號很有可能會超過該值,因此CLR的程式集版本號不用改修訂號。只需要修改檔案版本號即可。

由於在編譯時,VS會編譯AssemblyInfo檔案提取出程式集資訊放入到程式集內。我們直接複製出來的版本號模板檔案預設也會進行編譯。而我們建立的版本號模板檔案用於生成版本號檔案,無需編譯。我們需要的是通過版本號模板檔案生成版本號檔案,即通過AssemblyInfo.template.cs生成AssemblyInfo.cs。因此在版本號模板檔案右鍵屬性中將Build ActionCompile修改為None

20190629221252.png

此時我們已經有了版本號模板檔案,接下來要做的是在編譯的之前先根據版本號模板檔案建立我們需要的版本號檔案。
VS編譯的時候提供了編譯前預處理功能和編譯後處理功能。在程式集屬性中,我們選擇Build Event裡面有Pre-build event command line,通過在裡面輸入指令可以實現在編譯前執行我們想要的命令。

20190629181431.png

同時VS內部也提供了一些巨集指令供我們使用,通過點選Edit Pre-build按鈕,會彈出一個編輯框

20190629181601.png

點選Macros可以檢視所有VS支援的巨集指令

20190629181733.png

SubWCRev程式命令格式為SubWCRev WorkingCopyPath [SrcVersionFile DstVersionFile] [-nmdfe],WorkingCopyPath為SVN的工作副本,SrcVersionFile為原始版本檔案,即版本模板檔案。DstVersionFile為替換關鍵子後儲存的版本檔案。
在VS環境變數中我們可以通過$(ProjectDir)獲取到當前程式集路徑,通過$(SolutionDir)獲取到解決方案路徑。

巨集指令為$(指令名)格式

在預編譯事件中輸入以下指令SubWCRev $(SolutionDir) $(ProjectDir)Properties\AssemblyInfo.template.cs $(ProjectDir)Properties\AssemblyInfo.cs即可在編譯前獲取到SVN的reversion填充到修訂號中。

編譯後可以在輸出視窗看到關鍵字替換的資訊

1>------ Build started: Project: FGMain, Configuration: Debug Any CPU ------
1>  SubWCRev: 'F:\工作\SVN\Platform\trunk\FGMain\FGMain\'
1>  Last committed at revision 100268
1>  Mixed revision range 100267:100268
1>  Local modifications found
1>  Unversioned items found

獲取需求號

在實際工作中,我們每次發版都會有一個需求版本號。當產生需求時整個版本都會使用這個版本號。因此我們可以在開發的時候就在開發分支上建立該版本號的需求分支。分支名稱以版本號命名,這樣程式就可以獲取到URL的版本號資訊填充到版本號模板號模板檔案中。而省去了人為修改版本號的麻煩。

比如當前版本號為1.32.0,則在SVN程式的分支上建立一個1.32.0的版本。branches/FGMain/1.32.0

接下來在我們使用SubWCRev程式關鍵字替換之前需要先獲取到分支的版本號填充到版本號模板檔案中。這樣在編譯前就會將版本號和SVN的修訂號一同生成。
我們還需要提前判斷當前SVN工作目錄是否有修改,只有在工作目錄有修改時,才需要更新版本號,工作目錄沒有修改時,則無需修改版本號。

當我們安裝了SVN客戶端後(同時需要選擇安裝命令列工具),我們可以通過SVN執行執行命令,通過SVN help檢視支援的所有引數。

獲取版本號

我們需要獲取url的版本號。而版本號只有在分支目錄上才有,因此我們可以通過正則解析以下url,提取版本號。若提取不到則無需執行後續邏輯

通過svn info獲取當前目錄的svn資訊,通過svn info 路徑獲取指定路徑的svn資訊。

F:\工作\SVN\Platform\trunk\FGMain>svn info
Path: .
Working Copy Root Path: F:\工作\SVN\Platform\trunk\FGMain
URL: http://inner.svn.com:81/ATS_Code/Platform/branches/FGMain/1.32.0
Relative URL: ^/Platform/branches/FGMain/1.32.0
Repository Root: http://inner.svn.com:81/ATS_Code
Repository UUID: 2fd9d0ce-2897-f849-b9e2-af1303b08de7
Revision: 99512
Node Kind: directory
Schedule: normal
Last Changed Author: wish
Last Changed Rev: 99512
Last Changed Date: 2019-06-14 17:54:47 +0800 (週五, 14 6月 2019)

命令會返回多行資訊,我使用的時SVN 1.11 版本的客戶端,其他版本可能會有不同。我們解析第二行的URL從而解析出URL的版本號。

$svnInfo = svn info $projectDir
$urlInfo = $svnInfo[2]
$url = $urlInfo.Replace("URL: ","");
$urlMatchStr= 'branches/(.*?)/(.*?)/(.*?)'
if($url -notmatch $urlMatchStr)
{
    # 主線不再處理
    Write-Host "$url not match $urlMatchStr"
    return 
}

這裡需要注意由於我們當前目錄不一定就是解決方案目錄,在VS中我們實在解決方案呼叫的編譯工作,但是在jenkins我們的目錄可能會是bin/releasebin/debug,因此匹配URL時需要用非貪婪匹配。這樣無論路徑為branches/FGMain/1.32.0/FGBussness還是branches/FGMain/1.32.0/FGMain/bin/Debug 第二項都可以匹配到版本號。
現在同$matches[2]即可獲取到我們獲取到的版本號。

獲取當前工作副本狀態

當獲取到版本號時,表明當前實在分支目錄,則需要判斷工作副本是否有修改。有修改則需要更新版本號。通過svn status 檢視路徑的svn狀態,通過svn status 路徑可以檢視指定路徑的SVN狀態。

PS F:\工作\SVN\Platform\trunk\FGMain> svn status FGBussness
    ?       FGBussness\FGClientBussness.csproj.user
    M       FGBussness\MainWorkServer.cs
    ?       FGBussness\app.config
    ?       FGBussness\bin

命令返回了一個集合,每一行是一個檔案或資料夾的SVN狀態。SVN共包含以下狀態

  • " ": 無修改
  • "A": 新增
  • "C": 衝突
  • "D": 刪除
  • "G": 合併
  • "I": 忽略
  • "M": 改變
  • "R": 替換
  • "X": 未納入版本控制,但被外部定義所用
  • "?": 未納入版本控制
  • "!": 該專案已遺失 (被非 svn 命令所刪除) 或是不完整
  • "~": 版本控制下的專案與其它型別的專案重名
  • "L": 鎖定
  • "S": 已切換
  • "K": 存在鎖定標記

可以看到" "、"X"、"?"可以認為是本地無修改。其他狀態都有修改,需要更新版本號。當有衝突時,編譯也會出錯,同時編輯完衝突有可能就沒有修改了,因此狀態為"C"時也認為時無修改。


$svnStatuses = svn status $projectDir
#遍歷每個檔案狀態
foreach($svnStatus in $svnStatuses)
{
    $status = $svnStatus.SubString(0,1)
    if(($status -ne " ") -and ($status -ne "X") -and ($status -ne "?") -and ($status -ne "C"))
    {
        #存在編輯
        Write-Host $svnStatus.SubString(1).Trim()"Modified"
        $modified = $true
        break
    }
}

通過$modified記錄當前工作副本的是否修改。同時只要一個檔案修改了就無需判斷其他檔案。

更新版本號模板

接下來我們讀取版本號模板檔案,首先我們需要確認一下VS儲存的檔案編碼,我們按照VS的編碼讀取並儲存檔案。
檔案-高階儲存選項中可以看到設定的文字編碼

20190629200536.png

$versionContent = Get-Content $versionFile -encoding UTF8

for($count = 0 ; $count -lt $versionContent.Length; $count++)
{
    if(($versionContent[$count] -match '\[assembly: AssemblyVersion\(\"(\d*
    \.\d*\.\d*)\"\)\]') -or
    ($versionContent[$count] -match '\[assembly: AssemblyFileVersion\(\"(\d*\.\d*\.\d*)\.\$WCREV\$\"\)\]'))
    {
        #版本號不一致則更新版本號
        if($matches[1] -ne $marjorVersion)
        {
            Write-Host "Change Version"$matches[1]"To $marjorVersion"
            $versionContent[$count] = $versionContent[$count] -replace $matches[1],$marjorVersion
        }
        continue
    }
}

\d*\.\d*\.\d*匹配3位版本號,如1.32.0

遍歷檔案的每一行進行匹配,若匹配上了則將匹配的版本號替換為新的版本號。
最後更新版本號模板檔案
Set-Content $versionContent -Path $versionFile -encoding UTF8

同時由於我們程式只能獲取一個程式集當作整個程式的版本號,因此我們每次編譯的時候可以將啟動項強制更新版本號。我們可以新增一個$force 當設定為true的時候不管本地是否有修改都更新版本號。

完整的指令碼如下:

param([string] $projectDir,[string]$versionFile, $force)

Write-Host "current path:"$projectDir

try
{
    # 指定路徑
    $svnInfo = svn info $projectDir
    $urlInfo = $svnInfo[2]
    $url = $urlInfo.Replace("URL: ","");

    Write-Host "url:$url"

    $urlMatchStr= 'branches/(.*?)/(.*?)/(.*?)'
    if($url -notmatch $urlMatchStr)
    {
        # 主線不再處理
        
        Write-Host "$url not match $urlMatchStr"
        return 
    }
    # 分支
    # PS F:\工作\SVN\Platform\trunk\FGMain> $matches
    # Name                           Value
    # ----                           -----
    # 3                              FGBussness
    # 2                              1.32.0
    # 1                              FGMain
    # 0                              branches/FGMain/1.32.0/FGBussness
    $marjorVersion = $matches[2]
    Write-Host "Current Working Copy Version:$marjorVersion"

    # 沒有強制修改,則需要判斷當前工作路徑是否編輯過。
    $modified = $force 

    if($modified)
    {
        Write-Host "Force Modified"
    }
    else
    {
        #當路徑含有中文時,引數傳入會亂碼。暫時獲取當前路徑狀態
        $svnStatuses = svn status $projectDir
        #遍歷每個檔案狀態
        foreach($svnStatus in $svnStatuses)
        {
            $status = $svnStatus.SubString(0,1)
            if(($status -ne "X") -and ($status -ne "?"))
            {
                #存在編輯
                Write-Host $svnStatus.SubString(1).Trim()"Modified"
                $modified = $true
                break
            }
        }
    }
    # 若當前工作目錄沒有修改過的檔案則無需修改版本號
    # 查詢模板檔案的路徑
    if($modified)
    {
        Write-Host "Version File :$versionFile"
        $versionContent = Get-Content $versionFile -encoding UTF8

        for($count = 0 ; $count -lt $versionContent.Length; $count++)
        {
            if(($versionContent[$count] -match '\[assembly: AssemblyVersion\(\"(\d*\.\d*\.\d*)\"\)\]') -or
            ($versionContent[$count] -match '\[assembly: AssemblyFileVersion\(\"(\d*\.\d*\.\d*)\.\$WCREV\$\"\)\]'))
            {
                #版本號不一致則更新版本號
                if($matches[1] -ne $marjorVersion)
                {
                    Write-Host "Change Version"$matches[1]"To $marjorVersion"
                    $versionContent[$count] = $versionContent[$count] -replace $matches[1],$marjorVersion
                }
                continue
            }
        }
        # 編輯過則將模板的版本號替換掉
        # 在VS的選單-檔案-高階儲存選項中預設的檔案編碼是使用UTF8 With BOM的格式
        Set-Content $versionContent -Path $versionFile -encoding UTF8
    }
    else
    {
        Write-Host "No Modified"
    }
}
catch
{
    $Error
}

設定編譯前讀取版本號

指令碼編寫好,我們將指令碼放到專案根目錄下,這樣所有的程式集都能通過解決資料夾獲取到該指令碼。

powershell -ExecutionPolicy Bypass -NoProfile -NonInteractive -File $(SolutionDir)Update-Version.ps1 $(ProjectDir) $(ProjectDir)Properties\AssemblyInfo.template.cs

Pre-build event command line 新增以上命令呼叫更新版本號的指令碼。

  • -ExecutionPolicy Bypass表示允許該指令碼執行,否則可能沒有許可權執行本地指令碼檔案。
  • -NoProfile 表示不載入powershell的配置檔案。預設會加powershell所有的配置檔案。
  • -NonInteractive 表示不向使用者顯示互動式提示。

現在完整的Pre-build命令如下

powershell -ExecutionPolicy Bypass -NoProfile -NonInteractive -File $(SolutionDir)Update-Version.ps1 $(ProjectDir) $(ProjectDir)Properties\AssemblyInfo.template.cs
SubWCRev $(SolutionDir) $(ProjectDir)Properties\AssemblyInfo.template.cs $(ProjectDir)Properties\AssemblyInfo.cs

若啟動項預設需要強制更新版本號,則使用以下命令

powershell -ExecutionPolicy Bypass -NoProfile -NonInteractive -File $(SolutionDir)Update-Version.ps1 $(ProjectDir) $(ProjectDir) $true

若當前版本檔案的版本號為1.31.0,在1.32.0的分支上進行編譯,則會在輸出視窗輸出以下日誌

1>------ Build started: Project: FGMain, Configuration: Debug Any CPU ------
1>  current path: F:\工作\SVN\Platform\trunk\FGMain\FGMain\
1>  url:http://124.160.27.118:81/ATS_Code/Platform/branches/FGMain/1.32.0/FGMain
1>  Current Working Copy Version:1.32.0
1>  Force Modified
1>  Version File :F:\工作\SVN\Platform\trunk\FGMain\FGMain\Properties\AssemblyInfo.template.cs
1>  Change Version 1.31.0 To 1.32.0
1>  Change Version 1.31.0 To 1.32.0
...

總結

在指令碼編寫的時候遇到了以下錯誤

  1. 我們可以在傳入引數設定$force為bool型別,但是在外部呼叫powershell指令碼傳參傳入bool型別會報以下錯誤
    無法處理對引數“force”的引數轉換。無法將值“System.String”轉換為型別“System.Boolean”。布林引數僅接受布林值和數字,例如 $True、$False、1 或 0。
    但是通過提示的傳入值仍然會報錯,因此我們只能將[bool]顯示的型別去掉,避免強制轉換時出現錯誤。

  2. 外部傳入路徑含有中文會導致powershell由於亂碼處理不了

參考文獻

  1. 給PowerShell指令碼傳遞一個布林值
  2. Using PowerShell in post/pre build action in Visual Studio
  3. How to pass boolean values to a PowerShell script from a command prompt
  4. Always Use -NoProfile To Launch Scripts

本文地址:https://www.cnblogs.com/Jack-Blog/p/11108136.html
作者部落格:傑哥很忙
歡迎轉載,請在明顯位置給出出處及連結

相關文章