持续交付之.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 集中化处理。

本文源码:

  • https://github.com/zuozewei/blog-example/tree/master/Jenkins-ci/jenkins-net-versionpackage-python
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zuozewei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值