持续交付之.NET项目版本管理及技术落地(Python版)
一、前言
在上文中我们已经介绍基于 Git Flow
模型代码分支管理策略,同时为保证能给客户持续提供高品质的产品,保持项目稳定性,增强产品价值输出的节奏感。同时,为了规范工作流程,给客户提供明确的版本信息,固定产品发版策略以及分支管理规则提出要求,促使项目团队内认识一致,行为动作标准一致。
二、版本管理需求
1、版本号说明
格式:A.BB.CCCC.DDDD
例如:2.1.1001.1046
- A – 主版本号,代表是第几代产品;
- BB – 次版本号,功能集代号,每个季度加1;
- CCCC – 迭代号,每次功能迭代发布加1,一般两周一次迭代;
- DDDD – 修订号,任何代码级的变动都会造成本修订号增加;
2、发版节奏
每个季度末,发布一个大功能集合的稳定版本,这个版本会新增大量新的功能特性,并累积修复各种Bug,每发版1次,次版本号加1。这个是优先推荐各新项目采用、正在实施的项目尽快升级的版本。
每两周,发布一个预览版本,每次发布迭代号加1。这个版本会新增一些功能,修改了大部分已知的Bug。但是由于无法保证投入足够的测试力量进行各种全面的测试,所以可能会有不稳定或有未考虑到的不兼容问题。这个版本不建议直接用到生产环境,如果想用于生产环境,需项目实施组先在测试环境部署,确认没有遇到问题再部署到生产环境。
不定期,产品组会不定期发布稳定版的补丁版,这个版本基于每月1次的稳定版,产品组如果发现了对产品影响较大的Bug,会启动 hotfix
流程,基于当前最新的稳定版进行 Bug 修改,测试后发布替代原稳定版。这个版本的版本号会增加修订号,版本号其他部分不变。同样推荐新项目采用,也推荐正在实施的项目升级。
每日,Jenkins 每天早上都会自动编译程序的最新的版本,测试人员每日取这个最新版本进行新功能测试、自动化测试等工作。
3、版本文件名规则
- 正式版(Master 分支):
xxxx-20190207_1532-2.1.1001.1046.zip
- 预览版(Release 分支,每两周):
xxxx-20190207_1532-2.1.1001.1046.zip
- 开发版(Dev 分支,每日自动编译版本):
xxxx-20190207_1532-2.1.1001.1046.zip
- 补丁版(hotfixs 分支,不定期编译版本):
xxxx-20190207_1532-2.1.1001.1046.zip
三、解决方案
1、整体设计
2、主要方案
- 使用 Jenkins 调度整体流程
- 使用 Python 脚本获取 Gitlab 提交次数及
GlobalAssemblyInfo.cs
版本号 - 使用 Python 脚本写入
GlobalAssemblyInfo.cs
编译号 - 使用 Python 脚本压缩并打包编译文件
- 使用 Python 脚本写入版本号配置文件
- 使用 Python 脚本清理包文件及编译目录
- 使用 Python 脚本打包上传
Nexus OSS
并实现钉钉自动通知及下载
3、核心步骤
1)Jenkins 使用 Version Number
插件生成部分版本号
2) Python 获取 Gitlab 提交次数(编译号)
# coding=utf-8
import gitlab
# gitlab地址
url = 'http://xx.xx.xx.xx:xx'
token = 'xxxxxxxxxxxxx'
# 登录
gl = gitlab.Gitlab(url, token)
# 获取v3c仓库提交次数
def getcommits():
# 获取指定项目
project = gl.projects.get(197)
sum = 0
list = ['Army','dev','hotfixes','log','master','release']
for li in list:
commits = project.commits.list(all=True,query_parameters={'ref_name': li})
print(li +":" + str(len(commits)))
sum += len(commits)
return sum
3)Python 获取 GlobalAssemblyInfo.cs
版本号,并写入配置文件
# coding=utf-8
import os,re,configparser
# 接收jenkins当前JOB_NAME参数
workSpace = os.getenv("WORKSPACE")
JENKINS_HOME = os.getenv("JENKINS_HOME")
# 分支提交总次数
Revision = str(getcommits())
Major = ''
Minor = ''
Build = ''
path = workSpace+"\GlobalAssemblyInfo.cs"
versionPath = JENKINS_HOME+"\workspace\Version.ini"
config = configparser.ConfigParser()
with open(path,'r', encoding='UTF-8') as f:
lines = list(f)
new_lines = list()
changed = False
for line in lines:
if "Revision = " in line:
line = ' public const string Revision = "'+ Revision +'";\n'
changed = True
elif "Major = " in line:
Major = re.sub("\D", "", line)
elif "Minor = " in line:
Minor = re.sub("\D", "", line)
elif "Build = " in line:
Build = re.sub("\D", "", line)
else:
pass
new_lines.append(line)
if not changed:
continue
with open(path, 'w',encoding='UTF-8') as f:
f.write("".join(new_lines))
FullVersion = Major + "." + Minor + "." + Build + "." + Revision
config['xxxx'] = {
'xxxx_Major': Major,
'xxxx_Minor': Minor,
'xxxx_Build': Build,
'xxxx_Revision': Revision
}
with open(versionPath, 'w', encoding='UTF-8') as configfile:
config.write(configfile)
configfile.close()
f.close()
效果如下:
4)获取版本号信息
# coding=utf-8
import os, os.path, configparser
# 获取Jenkins变量
WORKSPACE = os.getenv("WORKSPACE")
JENKINS_HOME = os.getenv("JENKINS_HOME")
BUILD_VERSION = str(os.getenv("BUILD_VERSION"))
JOB_NAME = str(os.getenv("JOB_NAME"))
# 版本号信息
versionPath = JENKINS_HOME + "\workspace\Version.ini"
packageName = ""
xxx_Major = ''
xxx_Minor = ''
xxx_Build = ''
xxx_Revision = ''
def readVersion():
config = configparser.ConfigParser()
config.read(versionPath)
xxx_Major = config.get("xxx", "xxx_Major")
xxx_Minor = config.get("xxx", "xxx_Minor")
xxx_Build = config.get("xxx", "xxx_Build")
xxx_Revision = config.get("xxx", "xxx_Revision")
Version = BUILD_VERSION + '-' + xxx_Major + '.' + xxx_Minor + '.' + xxx_Build + '.' + xxx_Revision
zipName = Version + '.zip'
return zipName
5)压缩编译文件并打包
# coding=utf-8
import os, os.path, zipfile
# 使用zipfile做目录压缩
def zip_dir(dirname, zipfilename):
filelist = []
if os.path.isfile(dirname):
filelist.append(dirname)
else:
for root, dirs, files in os.walk(dirname):
for name in files:
filelist.append(os.path.join(root, name))
zf = zipfile.ZipFile(zipfilename, "w", zipfile.zlib.DEFLATED)
for tar in filelist:
arcname = tar[len(dirname):]
# print arcname
zf.write(tar, arcname)
print("【Build】zip is done!")
6)打包上传 Nexus OSS
仓库
# coding=utf-8
import nexuscli
def unloadNexus(loaclfile, repository):
nexus_client = nexuscli.nexus_client.NexusClient('http://xx.xx.xx.xx:8081', 'jenkins', 'jenkins')
upload_count = nexus_client.upload(loaclfile, repository)
print("【上传数量】:" + str(upload_count))
注意,Nexus OSS 需要创建好二进制包仓库,repository 为仓库路径地址。
7)包名写入配置文件,以备后续的自动通知使用
# coding=utf-8
import configparser
# 包名写入配置文件
def packageConifg(name, remotepath):
config = configparser.ConfigParser()
config['package'] = {
'name': name,
'remotedir': remotepath
}
with open(packagePath, 'w', encoding='UTF-8') as configfile:
config.write(configfile)
configfile.close()
print("【Build】packageConifg is done!")
效果如下:
8)清理包和编译文件夹
# coding=utf-8
import os, os.path, shutil
# 清空文件夹及ZIP文件
def cleanFile(bulidFilePath, zipPath):
# 判断文件夹是否存在
if os.path.exists(bulidFilePath):
shutil.rmtree(bulidFilePath)
print("【Build】cleanFile is done!")
# 判断zip文件是否存在
if os.path.exists(zipPath):
os.remove(zipPath)
print("【Build】cleanZIP is done!")
9)钉钉自动通知,并支持单击消息下载包
# coding=utf-8
'''
@author: zuozewei
@file: notification.py
@time: 2019/4/25 18:00
@description:dingTalk通知类
'''
import os, jenkins, configparser, requests, json
from dingtalkchatbot.chatbot import DingtalkChatbot
JOB_NAME = str(os.getenv("JOB_NAME"))
BUILD_URL = str(os.getenv("BUILD_URL")) + "console"
BUILD_VERSION = str(os.getenv("BUILD_VERSION"))
JENKINS_HOME = os.getenv("JENKINS_HOME")
BUILD_NUMBER = str(os.getenv("BUILD_NUMBER"))
WORKSPACE = os.getenv("WORKSPACE")
versionPath = JENKINS_HOME + "\workspace\Version.ini"
config = configparser.ConfigParser()
config.read(versionPath)
V3C_Major = config.get("v3c", "V3C_Major")
V3C_Minor = config.get("v3c", "V3C_Minor")
V3C_Build = config.get("v3c", "V3C_Build")
V3C_Revision = config.get("v3c", "V3C_Revision")
VERSION = V3C_Major + "." + V3C_Minor + "." + V3C_Build + "." + V3C_Revision
print("【当前版本】:" + VERSION)
packagePath = WORKSPACE + "\\package.ini"
def packagNotification():
title = 'xxx打包通知'
# 获取打包信息
packagconfig = configparser.ConfigParser()
packagconfig.read(packagePath)
packagName = packagconfig.get("package", "name")
packagRemotedir = packagconfig.get("package", "remotedir")
downloadlink = 'http://xxx.xxx.xxx.xxx:8081/repository/' + packagRemotedir + packagName
print("【Build】downloadlink:" + downloadlink)
text = '#### ' + JOB_NAME + ' - Package # ' + BUILD_NUMBER + ' \n' + \
'##### **版本类型**: ' + '预览版' + '\n' + \
'##### **当前版本**: ' + VERSION + '\n' + \
'##### **文件名称**: ' + packagName + '\n' + \
'##### **文件地址**: [点击下载](' + downloadlink + ') \n' + \
'> ###### xxxx技术团队 \n '
sendding(title, text)
def sendding(title, content):
at_mobiles = ['186xxxx2487']
Dingtalk_access_token_v3c = 'https://oapi.dingtalk.com/robot/send?access_token=xxxxxxx'
# 初始化机器人小丁
xiaoding = DingtalkChatbot(Dingtalk_access_token_v3c)
# Markdown消息@指定用户
xiaoding.send_markdown(title=title, text=content, at_mobiles=at_mobiles)
if __name__ == "__main__":
packagNotification()
四、注意事项
1、开发组长
开发组长需要维护 GlobalAssemblyInfo.cs
版本号
/*
* 全局程序集信息
* GlobalAssemblyInfo.cs
*
* 请把此文件引用到其他的项目中
*/
using System.Reflection;
[assembly: AssemblyProduct("xxxx")]
[assembly: AssemblyCompany("xxxx科技股份有限公司")]
[assembly: AssemblyCopyright("Copyright(C) e-xxxxx 2000-2020")]
[assembly: AssemblyVersion(RevisionClass.FullVersion)]
//产品版本号:统一使用2.0.0
[assembly: AssemblyInformationalVersionAttribute("2.0.1001")]
//程序中使用统一改为文件版本号
[assembly: AssemblyFileVersion(RevisionClass.FullVersion +
//只有Master、Release分支编译的版本才是正式版本,其他都是开发版。
#if !MASTER
"-开发版"
#else
""
#endif
)]
internal static class RevisionClass
{
public const string Major = "2";
public const string Minor = "0";
public const string Build = "1003";
public const string Revision = "36";
public const string MainVersion = Major + "." + Minor;
public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision;
}
- 每季度始需要修改
public const string Minor = " ";
的值,每季度加1; - 每两周始需要修改
public const string Build = " ";
的值,每两周加1; public const string Revision = " ";
的值由程序自动写入,无须处理;
2、其他同学
注意钉钉通知中的版本号信息
注意 exe 应用程序的详细信息
注意 Nexus OSS
仓库上传的文件包名:
五、小结
本文是.NET项目版本管理及技术落地的1.0版本,相对 Python 熟悉的同学上手起来比较快,扩展也比较灵活,后续考虑使用 Pipeline 集中化处理。
本文源码:
- 点赞
- 收藏
- 关注作者
评论(0)