前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >持续交付之.NET项目版本管理及技术落地(Python版)

持续交付之.NET项目版本管理及技术落地(Python版)

作者头像
高楼Zee
发布2019-10-24 11:02:29
6980
发布2019-10-24 11:02:29
举报
文章被收录于专栏:7DGroup
前言

在上文 持续交付之基于Git Flow代码分支策略实践 中我们已经介绍基于 GitFlow 模型代码分支管理策略,同时为保证能给客户持续提供高品质的产品,保持项目稳定性,增强产品价值输出的节奏感。同时,为了规范工作流程,给客户提供明确的版本信息,固定产品发版策略以及分支管理规则提出要求,促使项目团队内认识一致,行为动作标准一致。

版本管理需求

版本号说明

格式:A.BB.CCCC.DDDD 例如:2.1.1001.1046

  • A -- 主版本号,代表是第几代产品;
  • BB -- 次版本号,功能集代号,每个季度加1;
  • CCCC -- 迭代号,每次功能迭代发布加1,一般两周一次迭代;
  • DDDD -- 修订号,任何代码级的变动都会造成本修订号增加;

发版节奏

每个季度末,发布一个大功能集合的稳定版本,这个版本会新增大量新的功能特性,并累积修复各种Bug,每发版1次,次版本号加1。这个是优先推荐各新项目采用、正在实施的项目尽快升级的版本。

每两周,发布一个预览版本,每次发布迭代号加1。这个版本会新增一些功能,修改了大部分已知的Bug。但是由于无法保证投入足够的测试力量进行各种全面的测试,所以可能会有不稳定或有未考虑到的不兼容问题。这个版本不建议直接用到生产环境,如果想用于生产环境,需项目实施组先在测试环境部署,确认没有遇到问题再部署到生产环境。

不定期,产品组会不定期发布稳定版的补丁版,这个版本基于每月1次的稳定版,产品组如果发现了对产品影响较大的Bug,会启动 hotfix 流程,基于当前最新的稳定版进行 Bug 修改,测试后发布替代原稳定版。这个版本的版本号会增加修订号,版本号其他部分不变。同样推荐新项目采用,也推荐正在实施的项目升级。

每日,Jenkins 每天早上都会自动编译程序的最新的版本,测试人员每日取这个最新版本进行新功能测试、自动化测试等工作。

版本文件名规则

  • 正式版(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

解决方案

整体设计

主要方案

  • 使用 Jenkins 调度整体流程
  • 使用 Python 脚本获取 Gitlab 提交次数及 GlobalAssemblyInfo.cs 版本号
  • 使用 Python 脚本写入 GlobalAssemblyInfo.cs 编译号
  • 使用 Python 脚本压缩并打包编译文件
  • 使用 Python 脚本写入版本号配置文件
  • 使用 Python 脚本清理包文件及编译目录
  • 使用 Python 脚本打包上传 NexusOSS 并实现钉钉自动通知

核心步骤

1)Jenkins 使用 VersionNumber 插件生成部分版本号

2) Python 获取 Gitlab 提交次数(编译号)

代码语言:javascript
复制
# coding=utf-8
import gitlab

# gitlab地址
url = 'http://xx.xx.xx.xx:xx'
token = 'xxxxxxxxxxxxx'

# 登录
gl = gitlab.Gitlab(url, token)

# 获取仓库提交次数
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 版本号,并写入配置文件

代码语言:javascript
复制
# 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)获取版本号信息

代码语言:javascript
复制
# 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 = ""
V3C_Major = ''
V3C_Minor = ''
V3C_Build = ''
V3C_Revision = ''


def readVersion():
    config = configparser.ConfigParser()
    config.read(versionPath)
    V3C_Major = config.get("xxx", "xxx_Major")
    V3C_Minor = config.get("xxx", "xxx_Minor")
    V3C_Build = config.get("xxx", "xxx_Build")
    V3C_Revision = config.get("xxx", "xxx_Revision")
    Version = BUILD_VERSION + '-' + xxx_Major + '.' + xxx_Minor + '.' + xxx_Build + '.' + xxx_Revision
    zipName = Version + '.zip'
    return zipName

5)压缩编译文件并打包

代码语言:javascript
复制
# 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)打包上传 NexusOSS 仓库

代码语言:javascript
复制
# 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)包名写入配置文件,以备后续的自动通知使用

代码语言:javascript
复制
# 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)清理包和编译文件夹

代码语言:javascript
复制
# 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)钉钉自动通知,并支持单击消息下载包

代码语言:javascript
复制
# 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()

注意事项

开发组长

开发组长需要维护 GlobalAssemblyInfo.cs 版本号

代码语言:javascript
复制
/*
 * 全局程序集信息
 * 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;
}
  • 每季度始需要修改 publicconststringMinor=" "; 的值,每季度加1;
  • 每两周始需要修改 publicconststringBuild=" "; 的值,每两周加1;
  • publicconststringRevision=" ";的值由程序自动写入,无须处理;

其他同学

注意钉钉通知中的版本号信息

注意 exe 应用程序的详细信息

注意 NexusOSS 仓库上传的文件包名:

小结

本文是 .NET 项目版本管理及技术落地的1.0版本,相对 Python 熟悉的同学上手起来比较快,扩展也比较灵活,后续2.0考虑 Pipeline 集中化处理。

本文源码:

https://github.com/7DGroup/Jenkins-CI/tree/master/Jenkins-Net-VersionPackage-Python

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 7DGroup 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 版本管理需求
    • 版本号说明
      • 发版节奏
        • 版本文件名规则
        • 解决方案
          • 整体设计
            • 主要方案
              • 核心步骤
              • 注意事项
                • 开发组长
                  • 其他同学
                  • 小结
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档