目录
前言
目的
详细流程
获取 SVN Reversion
获取需求号
设置编译前读取版本号
总结
参考文献
目录
Jenkins 持续集成学习 - Windows 环境进行. Net 开发 1
Jenkins 持续集成学习 - Windows 环境进行. Net 开发 2
Jenkins 持续集成学习 - Windows 环境进行. Net 开发 3
Jenkins 持续集成学习 - Windows 环境进行. Net 开发 4
Jenkins 持续集成学习 - 搭建 jenkins 问题汇总
Jenkins 持续部署 - Windows 环境持续部署探究 1
前言
在上一篇之前的文章开始对 Windows 环境下持续部署的方案进行学习与研究. 上一篇文章主要介绍关于持续部署需要的一些技术方案的实现, 在本篇文章开始对持续部署的一些细节实现展开讨论.
本篇文章先对版本号的自动更新流程进行梳理和说明. 后续需要通过版本号比较创建差量更新包.
目的
本章文章主要是通过调用 SVN 客户端命令和 powershell 脚本实现完全无需人工干预自动生成版本号.
详细流程
若程序需要定义版本号, 则可以将版本号记录在程序集的 AssemblyInfo.cs 文件中
- [assembly: AssemblyVersion("1.0.0")]
- [assembly: AssemblyFileVersion("1.0.0")]
也可以在程序集右键选择属性(或者通过快捷键 Alt + Enter), 在 Application 点击 Assembly Infomation... 按钮修改程序集版本号和文件版本号.
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 程序替换修订号.
由于 AssemblyVersion 有大小限制, 不允许超过 65535, 而 SVN 修订号很有可能会超过该值, 因此 CLR 的程序集版本号不用改修订号. 只需要修改文件版本号即可.
由于在编译时, VS 会编译 AssemblyInfo 文件提取出程序集信息放入到程序集内. 我们直接复制出来的版本号模板文件默认也会进行编译. 而我们创建的版本号模板文件用于生成版本号文件, 无需编译. 我们需要的是通过版本号模板文件生成版本号文件, 即通过 AssemblyInfo.template.cs 生成 AssemblyInfo.cs. 因此在版本号模板文件右键属性中将 Build Action 从 Compile 修改为 None.
此时我们已经有了版本号模板文件, 接下来要做的是在编译的之前先根据版本号模板文件创建我们需要的版本号文件.
VS 编译的时候提供了编译前预处理功能和编译后处理功能. 在程序集属性中, 我们选择 Build Event 里面有 Pre-build event command line, 通过在里面输入指令可以实现在编译前执行我们想要的命令.
同时 VS 内部也提供了一些宏指令供我们使用, 通过点击 Edit Pre-build 按钮, 会弹出一个编辑框
点击 Macros 可以查看所有 VS 支持的宏指令
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/release 或 bin/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 的编码读取并保存文件.
在文件 - 高级保存选项中可以看到设置的文本编码
- $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
- ...
总结
在脚本编写的时候遇到了以下错误
我们可以在传入参数设置 $force 为 bool 类型, 但是在外部调用 powershell 脚本传参传入 bool 类型会报以下错误
无法处理对参数 "force" 的参数转换. 无法将值 "System.String" 转换为类型 "System.Boolean". 布尔参数仅接受布尔值和数字, 例如 $True,$False,1 或 0.
但是通过提示的传入值仍然会报错, 因此我们只能将 [bool] 显示的类型去掉, 避免强制转换时出现错误.
外部传入路径含有中文会导致 powershell 由于乱码处理不了
参考文献
给 PowerShell 脚本传递一个布尔值 https://www.pstips.net/pass-boolean-to-script.html
- Using PowerShell in post/pre build action in Visual Studio
- How to pass boolean values to a PowerShell script from a command prompt
- Always Use -NoProfile To Launch Scripts http://www.powertheshell.com/bp_noprofile/
本文地址: https://www.cnblogs.com/Jack-Blog/p/11108136.html
作者博客: 杰哥很忙
来源: https://www.cnblogs.com/Jack-Blog/p/11108136.html