Files
GitVer/README.md
2026-05-21 20:21:51 +08:00

669 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GitVer
命令行版本管理工具,从 Git 仓库读取 tag 自动生成并回写版本号。
---
## 用法总览
```
gitver
gitver rewrite [PEType] [-f]
gitver setver=<pid> [repodir=<path>] [-test] [-setup=0|1]
gitver nuitkabuild=<pid> [repodir=<path>] [-test] [params="<mainPy> <nuitka参数>"]
gitver nuitkapydbuild=<pid> [repodir=<path>] [-test] [params="<modulePy> <nuitka参数>"]
gitver pyinstaller=<pid> [repodir=<path>] [-test] [params="<mainPy|specFile> <pyinstaller参数>"]
gitver setup=n [pid=m] [repodir=<path>] [-test]
```
**通用参数说明:**
| 参数 | 说明 |
|---|---|
| `pid` | 产品 ID整数范围 0-65535内嵌在命令名中`setver=5` |
| `repodir=<path>` | Git 仓库目录,缺省使用当前运行目录 |
| `-test` | 将产品版本号的 major/minor 置为 0测试版本构建 |
| `params="..."` | 传递给 Nuitka 或 PyInstaller 的参数(**含入口文件名**),用双引号括起来 |
**BID 规则:**
`main`/`master` 分支固定 `bid=0`;其他分支格式须为 `<描述>.<数字>`,自动读取末尾数字作为 `bid`
---
## 命令详解
### `gitver`(无参数交互模式)
自动读取当前分支最近三次符合 `<分支>.major.minor` 格式的 tag提示用户选择 major+1 或 minor+1二次确认后创建新 tag。
当前分支无有效 tag 时进入首发引导:提示输入默认 major/minor回车使用默认 1/0再创建首个 tag。
**交互示例(已有 tag**
```
当前分支: main最近三个 tag
main.1920.9
main.1920.10
main.1920.11
输入 1 → 创建 main.1921.0
输入 2 → 创建 main.1920.12
```
**交互示例(无 tag 首发):**
```
未找到符合规则的历史标签
输入默认 major回车=1: 2
输入默认 minor回车=0: 5
→ 创建 <分支>.2.5
```
---
### `gitver rewrite`
自动识别当前目录源码类型C++、C#、Python执行默认版本回写。
```
gitver rewrite [PEType] [-f]
```
| 参数 | 说明 |
|---|---|
| `PEType` | 可选,`1`=EXE默认`2`=DLL最多出现一次 |
| `-f` | 强制模式:未找到目标文件或回写失败仅提示,不返回错误码 35 |
**回写行为:**
- **C++**:递归查找首个 `.rc` 文件,回写 `FILEVERSION``PRODUCTVERSION``VALUE "FileVersion"``VALUE "ProductVersion"`(限定在 `VS_VERSION_INFO` 版本块内)
- **C#**:递归查找首个 `AssemblyInfo.cs`,回写 `AssemblyVersion``AssemblyFileVersion`
- **Python**:不执行回写,提示改用 `nuitkabuild=``nuitkapydbuild=`
**示例:**
```
gitver rewrite
gitver rewrite 2
gitver rewrite -f
```
---
### `gitver setver=<pid>`
从 Git 仓库读取 tag生成产品版本号与文件版本号并按源码类型自动回写。
```
gitver setver=<pid> [repodir=<path>] [-test]
```
**版本号生成规则:**
- `ProductVersion = pid.bid.major.minor`(取当前分支前缀的最新 tag
- `FileVersion = pid.yy.mmdd.id`id = 当天当前分支提交次数)
**示例:**
```
gitver setver=5
gitver setver=5 repodir=E:\Code\OTH\gitver
gitver setver=5 -test
```
**输出示例:**
```
ProductVersion=5.0.1920.11
FileVersion=5.26.0519.3
```
**回写说明:**
- **C++**:回写 `.rc` 文件中的 FILEVERSION、PRODUCTVERSION 及对应字符串字段
- **C#**:回写 `AssemblyInfo.cs` 中的 AssemblyVersion、AssemblyFileVersion
- **Python**:仅输出版本号,不执行文件回写,提示改用 Nuitka 命令
- 编码:支持 ANSI/UTF-8保留 UTF-8 BOMUTF-16 文件会报错停止
- 当前分支无匹配 tag 时自动使用默认版本 `1.0`
---
### `gitver nuitkabuild=<pid>`
生成版本号并调用 Nuitka 打包 Python 程序EXE 模式)。
```
gitver nuitkabuild=<pid> [repodir=<path>] [-test] [params="<mainPy> <nuitka参数>"]
```
自动调用:
```
python -m nuitka --windows-product-version=<版本> --windows-file-version=<版本> <params>
```
**示例:**
```
gitver nuitkabuild=5 params="main.py"
gitver nuitkabuild=5 -test params="main.py"
gitver nuitkabuild=5 repodir=E:\Code\MyPyProj params="src\\app.py --standalone --output-dir=dist"
```
**输出示例:**
```
Nuitka 打包开始..
ProductVersion=5.0.1920.11
FileVersion=5.26.0519.3
```
> 需要当前环境已安装 Nuitka可通过 `python -m nuitka --version` 验证)。
---
### `gitver nuitkapydbuild=<pid>`
生成版本号并调用 Nuitka 打包 Python 模块pyd/DLL 模式)。
```
gitver nuitkapydbuild=<pid> [repodir=<path>] [-test] [params="<modulePy> <nuitka参数>"]
```
自动调用:
```
python -m nuitka --module --windows-product-version=<版本> --windows-file-version=<版本> <params>
```
**示例:**
```
gitver nuitkapydbuild=5 params="module.py"
gitver nuitkapydbuild=5 -test params="module.py"
gitver nuitkapydbuild=5 repodir=E:\Code\MyPyProj params="src\\core.py --output-dir=dist"
```
> 需要当前环境已安装 Nuitka可通过 `python -m nuitka --version` 验证)。
---
### `gitver pyinstaller=<pid>`
生成版本号并调用 PyInstaller 打包 Python 程序,支持直接传入 `.py` 入口脚本或 `.spec` 构建配置文件。
```
gitver pyinstaller=<pid> [repodir=<path>] [-test] [params="<mainPy|specFile> <pyinstaller参数>"]
```
自动调用:
```
python -m PyInstaller <params>
```
版本号照常从 Git tag 计算并输出。在 **spec 模式**下gitver 会在调用 PyInstaller 前自动将版本号写入版本信息,具体行为取决于 spec 文件中 `version=` 字段的值:
| spec 中 `version=` 的值 | 行为 |
|---|---|
| `'version_info.txt'`(引号路径,文件存在) | 就地更新外部版本信息文件 |
| `'1.2.3.4'`(引号字符串,非有效文件路径) | 替换 spec 内该引号字符串为新版本号 |
| `version_info`无引号标识符spec 内定义的 VSVersionInfo 对象) | 就地更新 spec 文件内的版本字段 |
| 无 `version=` / `version=None` | 在 spec 目录生成 `version_info.txt` 并自动注入 `version='version_info.txt'` |
**脚本模式**`.py` 入口PyInstaller 不接受命令行版本参数,版本号仅输出到控制台,不嵌入 EXE。
**两种打包模式:**
| 模式 | 参数示例 | 说明 |
|---|---|---|
| 脚本模式 | `params="main.py"` | 由 PyInstaller 自动生成 spec 并打包 |
| spec 模式 | `params="myapp.spec"` | 使用已有 spec 文件,打包配置完全由 spec 控制gitver 在调用 PyInstaller 前**自动将版本号写入版本信息**(见下表) |
**示例:**
```
gitver pyinstaller=5 params="main.py"
gitver pyinstaller=5 -test params="main.py"
gitver pyinstaller=5 repodir=E:\Code\MyPyProj params="src\\app.py --onefile --name=MyApp"
gitver pyinstaller=5 params="myapp.spec"
gitver pyinstaller=5 -test params="myapp.spec"
gitver pyinstaller=5 repodir=E:\Code\MyPyProj params="myapp.spec"
```
**输出示例:**
```
PyInstaller 打包开始.. (main.py)
ProductVersion=5.0.1920.11
FileVersion=5.26.0519.3
```
> 需要当前环境已安装 PyInstaller可通过 `python -m PyInstaller --version` 验证)。
---
### `gitver setup=n`(独立打包命令)
独立调用安装脚本打包,不依赖其他命令。
```
gitver setup=n [pid=m] [repodir=<path>] [-test]
```
| 参数 | 说明 |
|---|---|
| `n` | 安装脚本类型,`0`=Inno Setup`setup.iss``1`=NSIS`setup.nsi` |
| `pid=m` | 可选,产品 ID整数 0-65535不指定时自动从项目的 `.rc``AssemblyInfo.cs``.spec` 文件中读取版本信息 |
**示例:**
```
gitver setup=0
gitver setup=1
gitver setup=0 pid=5
gitver setup=1 pid=5 repodir=E:\Code\MyProj
```
---
## Nuitka 与 PyInstaller 常用参数速查
### Nuitka 常用参数
通过 `params="..."` 传递给 `gitver nuitkabuild=` / `nuitkapydbuild=`
| 参数 | 说明 |
|---|---|
| `--standalone` | 独立模式:将所有依赖打包进输出目录,可单独分发 |
| `--onefile` | 单文件模式:将独立包压缩为单个 EXE需配合 `--standalone` |
| `--output-dir=<dir>` | 指定输出目录,如 `--output-dir=dist` |
| `--windows-icon-from-ico=<ico>` | 设置 EXE 图标 |
| `--windows-company-name=<name>` | 设置公司名(写入文件属性) |
| `--windows-product-name=<name>` | 设置产品名 |
| `--enable-plugin=<plugin>` | 启用插件,如 `pyside6``tk-inter``numpy` |
| `--follow-imports` | 跟踪所有隐式导入(打包更完整,体积也更大) |
| `--nofollow-import-to=<mod>` | 排除指定模块不展开,如 `--nofollow-import-to=tests` |
| `--include-package=<pkg>` | 强制包含指定包 |
| `--include-data-files=<src>=<dst>` | 打包额外数据文件 |
| `--remove-output` | 打包前先清理上次输出 |
| `--lto=yes` | 启用链接时优化Release 构建推荐) |
| `--jobs=<n>` | 并行编译线程数,如 `--jobs=4` |
**典型用法示例:**
```bash
# 最小单文件 EXE
gitver nuitkabuild=5 params="main.py --standalone --onefile --output-dir=dist"
# 带图标、公司名的独立包
gitver nuitkabuild=5 params="main.py --standalone --windows-icon-from-ico=app.ico --windows-company-name=MyCompany --output-dir=dist"
# PySide6 GUI 应用
gitver nuitkabuild=5 params="main.py --standalone --enable-plugin=pyside6 --output-dir=dist"
# 打包 pyd 模块
gitver nuitkapydbuild=5 params="core.py --output-dir=dist"
# 异地仓库
gitver nuitkabuild=5 repodir=E:\Code\MyProj params="main.py --standalone --onefile --output-dir=dist"
```
---
### PyInstaller 常用参数
通过 `params="..."` 传递给 `gitver pyinstaller=`
| 参数 | 说明 |
|---|---|
| `--onefile` / `-F` | 单文件模式:打包成单个 EXE |
| `--onedir` / `-D` | 单目录模式(默认) |
| `--name=<name>` / `-n` | 指定输出文件名(不含后缀) |
| `--icon=<ico>` / `-i` | 设置 EXE 图标 |
| `--distpath=<dir>` | 指定输出目录,默认 `dist` |
| `--workpath=<dir>` | 中间文件目录,默认 `build` |
| `--specpath=<dir>` | spec 文件生成位置,默认当前目录 |
| `--noconsole` / `-w` | 不显示控制台窗口GUI 应用) |
| `--hidden-import=<mod>` | 手动指定隐式导入的模块 |
| `--add-data=<src>;<dst>` | 打包额外数据文件Windows 分隔符为 `;`Linux/macOS 为 `:` |
| `--version-file=<file>` | 从文件读入 Windows 版本信息嵌入 EXE |
| `--uac-admin` | 请求管理员权限运行 |
| `--clean` | 构建前清理缓存 |
| `--log-level=WARN` | 减少构建输出噪音,可选 `DEBUG``INFO``WARN``ERROR` |
**典型用法示例:**
```bash
# 最小单文件 EXE
gitver pyinstaller=5 params="main.py --onefile --name=MyApp"
# GUI 应用(关指控制台)
gitver pyinstaller=5 params="main.py --onefile --noconsole --icon=app.ico --name=MyApp"
# 包含资源文件
gitver pyinstaller=5 params="main.py --onefile --add-data=assets;assets --name=MyApp"
# 指定输出目录
gitver pyinstaller=5 params="main.py --onefile --distpath=dist --workpath=build --name=MyApp"
# 使用 spec 文件(配置均在 spec 中,不需要其他 params
gitver pyinstaller=5 params="myapp.spec"
# 使用 spec 并覆盖输出路径
gitver pyinstaller=5 params="myapp.spec --distpath=release"
# 异地仓库
gitver pyinstaller=5 repodir=E:\Code\MyProj params="main.py --onefile --name=MyApp"
```
**spec 文件嵌入版本信息(推荐方式):**
PyInstaller 不支持命令行版本参数。需将版本号嵌入 EXE 时,建议先用 `gitver setver=<pid>` 生成版本号,再在 spec 文件中设置 `version_file` 字段:
```python
# myapp.spec 片段
exe = EXE(
pyz,
...,
version='version_info.txt', # Windows 版本信息文件
name='MyApp',
)
```
**完整 spec 文件示例(`myapp.spec`**
```python
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['src\\main.py'], # 入口脚本
pathex=[],
binaries=[],
datas=[
('assets', 'assets'), # 额外资源目录:源路径, 目标路径
],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='MyApp',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False, # True=控制台程序False=GUI程序
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='assets\\app.ico', # 图标文件路径
version='version_info.txt', # Windows 版本信息文件
)
```
**配套的 `version_info.txt` 示例:**
```python
# UTF-8
# Windows EXE 版本信息文件
# 版本号格式major, minor, patch, build均为整数
VSVersionInfo(
ffi=FixedFileInfo(
filevers=(5, 0, 1920, 11), # FileVersion对应 FileVersion=5.0.1920.11
prodvers=(5, 0, 1920, 11), # ProductVersion
mask=0x3f,
flags=0x0,
OS=0x40004,
fileType=0x1, # 0x1=EXE, 0x2=DLL
subtype=0x0,
date=(0, 0),
),
kids=[
StringFileInfo([
StringTable(
'040904B0', # 语言 0409=英语, 04B0=Unicode
[
StringStruct('CompanyName', 'MyCompany'),
StringStruct('FileDescription', 'MyApp Application'),
StringStruct('FileVersion', '5.0.1920.11'),
StringStruct('InternalName', 'MyApp'),
StringStruct('LegalCopyright', 'Copyright (C) 2026 MyCompany'),
StringStruct('OriginalFilename', 'MyApp.exe'),
StringStruct('ProductName', 'MyApp'),
StringStruct('ProductVersion', '5.0.1920.11'),
]
)
]),
VarFileInfo([VarStruct('Translation', [0x0409, 0x04B0])])
]
)
```
> 在 spec 模式下gitver 会在调用 PyInstaller 前自动将计算出的版本号写入版本信息,无需手动维护 `version_info.txt`。
---
## 错误码
| 码 | 含义 |
|---|---|
| 2 | 默认回写流程下未获取到 git 提交号 |
| 3 | 参数非法或未知参数 |
| 4 | `setver=` 的 pid 非法 |
| 5 | `setver=` 无法根据分支名计算 bid |
| 9 | `setver=` 获取当天分支提交次数失败 |
| 15 | 回写流程未识别源码类型 |
| 16 | 回写流程识别到 Python请改用 nuitka 命令 |
| 17 | `nuitkabuild=` 参数不足(缺少 mainPy |
| 18 | `nuitkabuild=` 的 pid 非法 |
| 19 | `nuitkabuild=` 无法根据分支名计算 bid |
| 21 | `nuitkabuild=` 获取当天分支提交次数失败 |
| 22 | `rewrite` 参数非法PEType 非法或重复) |
| 23 | `repodir=` 不是有效目录 |
| 24 | `nuitkapydbuild=` 参数不足(缺少 modulePy |
| 25 | `nuitkapydbuild=` 的 pid 非法 |
| 26 | `nuitkapydbuild=` 无法根据分支名计算 bid |
| 28 | `nuitkapydbuild=` 获取当天分支提交次数失败 |
| 29 | 无参数模式下无法获取当前分支 |
| 30 | 无参数模式下读取标签列表失败 |
| 31 | 无参数模式下未读取到用户输入 |
| 32 | 无参数模式下用户选择非法,或 major/minor 已达上限 |
| 33 | 无参数模式下创建 tag 失败 |
| 34 | 无参数模式下创建 tag 后校验失败 |
| 35 | 未找到可回写目标文件,或回写失败 |
| 36 | `setup=` 值不支持(仅支持 0 和 1 |
| 37 | 未找到安装脚本setup.iss / setup.nsi |
| 38 | 修改安装脚本失败 |
| 39 | 未找到安装编译器ISCC.exe / makensis.exe |
| 40 | `pyinstaller=` 参数不足(缺少 mainPy 或 specFile |
| 41 | `pyinstaller=` 的 pid 非法 |
| 42 | `pyinstaller=` 无法根据分支名计算 bid |
| 43 | `pyinstaller=` 读取 tag 失败 |
| 44 | `pyinstaller=` 获取当天分支提交次数失败 |
| 45 | `pyinstaller=` 版本信息文件无法生成或更新 |
| 46 | `pyinstaller=` spec 文件无法注入或修改版本信息 |
---
## 最小自测
### 基础验证:`setver=`
```bash
# 初始化测试仓库
mkdir test_repo && cd test_repo
git init
echo test > README.txt
git add . && git commit -m "init"
git tag main.1920.10
git tag main.1920.11
# 验证版本生成
gitver setver=5
# 预期:
# ProductVersion=5.0.1920.11
# FileVersion=5.yy.mmdd.<id>
# 返回码: 0
```
### 验证无 tag 时自动使用默认版本
```bash
git checkout -b newbranch
gitver setver=5
# 预期: ProductVersion=5.<bid>.1.0(无 tag 自动回落默认 1.0
# 返回码: 0
```
### 验证分支 tag 隔离
```bash
git checkout -b feature.12
git tag feature.12.1930.7
git tag feature.12.1930.8
gitver setver=5
# 预期: ProductVersion=5.12.1930.8(忽略 main.* tag
# 返回码: 0
```
### 验证 `-test`
```bash
gitver setver=5 -test
# 预期: ProductVersion=5.0.0.0major/minor 置零)
# 返回码: 0
```
### 验证 `nuitkabuild=`
```bash
gitver nuitkabuild=5 params="main.py"
# 预期: 调用 python -m nuitka注入版本号参数
gitver nuitkabuild=5 params="src\\app.py --standalone --output-dir=dist"
# 预期: 额外参数传递给 Nuitka
```
### 验证 `pyinstaller=`
```bash
gitver pyinstaller=5 params="main.py"
# 预期: 调用 python -m PyInstaller main.py
# 输出 ProductVersion / FileVersion
gitver pyinstaller=5 params="myapp.spec"
# 预期: 调用 python -m PyInstaller myapp.specspec 模式)
gitver pyinstaller=5 params="main.py --onefile --name=MyApp"
# 预期: 额外参数传递给 PyInstaller
```
### 验证 `setup=`
```bash
# 确保 exe 目录或上级目录有 setup.iss
gitver setup=0
# 预期: 从 .rc 或 AssemblyInfo.cs 读取版本号,修改 setup.iss 并调用 ISCC.exe 编译
gitver setup=0 pid=5
# 预期: 使用 pid=5 生成版本号,修改 setup.iss 并调用 ISCC.exe 编译
```
---
## 回归清单
### rewrite 组
| 命令 | 预期返回码 |
|---|---|
| `gitver rewrite` | 0 / 15 / 16 / 35 |
| `gitver rewrite -f` | 0 / 15 / 16 |
| `gitver rewrite 2` | 0 / 15 / 35 |
| `gitver rewrite 1 2` | 22 |
### setver= 组
| 命令 | 预期返回码 |
|---|---|
| `gitver setver=5` | 0 |
| `gitver setver=5 -test` | 0 |
| `gitver setver=5 repodir=E:\NotExists` | 23 |
| `gitver setver=abc` | 4 |
### nuitkabuild= 组
| 命令 | 预期返回码 |
|---|---|
| `gitver nuitkabuild=5 params="main.py"` | 0 / 19 / 21 |
| `gitver nuitkabuild=5 -test params="main.py"` | 0 |
| `gitver nuitkabuild=5 params="main.py --standalone"` | 0 |
| `gitver nuitkabuild=5 repodir=E:\NotExists params="main.py"` | 23 |
| `gitver nuitkabuild=5` | 0 |
### nuitkapydbuild= 组
| 命令 | 预期返回码 |
|---|---|
| `gitver nuitkapydbuild=5 params="module.py"` | 0 / 26 / 28 |
| `gitver nuitkapydbuild=5 -test params="module.py"` | 0 |
| `gitver nuitkapydbuild=5 params="module.py --output-dir=dist"` | 0 |
| `gitver nuitkapydbuild=5 repodir=E:\NotExists params="module.py"` | 23 |
| `gitver nuitkapydbuild=5` | 0 |
### pyinstaller= 组
| 命令 | 预期返回码 |
|---|---|
| `gitver pyinstaller=5 params="main.py"` | 0 / 42 / 44 |
| `gitver pyinstaller=5 -test params="main.py"` | 0 |
| `gitver pyinstaller=5 params="main.py --onefile"` | 0 |
| `gitver pyinstaller=5 params="myapp.spec"` | 0 |
| `gitver pyinstaller=5 -test params="myapp.spec"` | 0 |
| `gitver pyinstaller=5 repodir=E:\NotExists params="main.py"` | 23 |
| `gitver pyinstaller=5` | 0 |
| `gitver pyinstaller=abc params="main.py"` | 41 |
### setup= 组
| 命令 | 预期返回码 |
|---|---|
| `gitver setup=0` | 0 / 37 / 38 / 39 |
| `gitver setup=1` | 0 / 37 / 38 / 39 |
| `gitver setup=0 pid=5` | 0 / 37 / 38 / 39 |
| `gitver setup=0 pid=5 repodir=E:\NotExists` | 23 |
| `gitver setup=9` | 36 |
| `gitver setup=0 pid=abc` | 4 |
> **注意**如果打包时提示“未找到安装编译器ISCC.exe / makensis.exe请手动下载安装对应工具并将其可执行文件路径如 `ISCC.exe` 或 `makensis.exe` 所在目录)添加到系统环境变量 `PATH`。常见下载地址:
>
> - Inno Setup: https://jrsoftware.org/isinfo.php
> - NSIS: https://nsis.sourceforge.io/Download
>
> 添加方法:
> 1. 复制编译器安装目录路径(如 `C:\Program Files (x86)\Inno Setup 6`)。
> 2. 打开“系统属性”→“高级”→“环境变量”,在“系统变量”中找到 `PATH`,点击“编辑”,添加上述路径。
> 3. 重新打开命令行窗口后再运行打包命令。