1、新增上传命令行参数:upload
2、上传参数关联函数调整与优化
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include "gitver_types.h"
|
||||
#include "gitver_version.h"
|
||||
#include "gitver_setup.h"
|
||||
#include "gitver_upload.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define new DEBUG_NEW
|
||||
@@ -355,6 +356,7 @@ void PrintFullUsageExamples()
|
||||
_T(" gitver nuitkapydbuild=<pid> [repodir=<path>可选] [-test可选] [params=\"<modulePy> <nuitka参数>\"可选]\n")
|
||||
_T(" gitver pyinstaller=<pid> [repodir=<path>可选] [-test可选] [params=\"<mainPy|specFile> <pyinstaller参数>\"可选]\n")
|
||||
_T(" gitver setup=n [pid=m可选] [repodir=<path>可选] [-test可选]\n")
|
||||
_T(" gitver upload username=<用户名> [file=<上传文件>] [info=<配置.txt>] [url=<PMC基址>]\n")
|
||||
_T(" setup=0: 使用 Inno Setup 脚本 (setup.iss)\n")
|
||||
_T(" setup=1: 使用 NSIS 脚本 (setup.nsi)\n")
|
||||
_T(" 不指定 pid 时自动从 .rc / AssemblyInfo.cs / version_info.txt 读取版本\n")
|
||||
@@ -381,6 +383,7 @@ void PrintFullUsageExamples()
|
||||
_T(" gitver setup=0\n")
|
||||
_T(" gitver setup=0 pid=5\n")
|
||||
_T(" gitver setup=1 pid=5 repodir=E:\\Code\\MyProj\n")
|
||||
_T(" gitver upload username=admin\n")
|
||||
_T("params: 传递给 Nuitka/PyInstaller 的参数(含入口文件),用双引号括起来,如 params=\"main.py --standalone --output-dir=dist\"。\n")
|
||||
_T("-test: 将产品版本号的 major 和 minor 都置为 0(用于测试版本构建)。\n")
|
||||
_T("无参数时会读取当前分支最近三次 tag,并提示选择 major 加 1 或 minor 加 1,然后创建新 tag。\n")
|
||||
@@ -399,6 +402,7 @@ void PrintShortCommandUsage()
|
||||
_tprintf(_T("或:gitver nuitkabuild=<pid> [repodir=<path>可选] [params=\"<mainPy> <nuitka参数>\"可选]\n"));
|
||||
_tprintf(_T("或:gitver nuitkapydbuild=<pid> [repodir=<path>可选] [params=\"<modulePy> <nuitka参数>\"可选]\n"));
|
||||
_tprintf(_T("或:gitver pyinstaller=<pid> [repodir=<path>可选] [params=\"<mainPy|specFile> <pyinstaller参数>\"可选]\n"));
|
||||
_tprintf(_T("或:gitver upload username=<用户名> [file=<上传文件>] [info=<配置.txt>] [url=<PMC基址>]\n"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -507,6 +511,20 @@ LPCTSTR GetExitCodeHint(int nRetCode)
|
||||
return _T("pyinstaller: write version_info.txt failed");
|
||||
case 46:
|
||||
return _T("pyinstaller: inject version into spec file failed");
|
||||
case 47:
|
||||
return _T("upload: missing required args");
|
||||
case 48:
|
||||
return _T("upload: file not found");
|
||||
case 49:
|
||||
return _T("upload: info invalid or env password missing");
|
||||
case 50:
|
||||
return _T("upload: login failed");
|
||||
case 51:
|
||||
return _T("upload: upload http failed");
|
||||
case 52:
|
||||
return _T("upload: version not found after upload");
|
||||
case 53:
|
||||
return _T("upload: network error");
|
||||
default:
|
||||
return _T("");
|
||||
}
|
||||
@@ -605,6 +623,8 @@ int HandleSetVerCommand(int argc, TCHAR* argv[])
|
||||
|
||||
CString strProductVersion;
|
||||
CString strFileVersion;
|
||||
CString strBranch;
|
||||
SYSTEMTIME stFileVersionDate = { 0 };
|
||||
VersionBuildErrorCodes errorCodes = { 5, 6, 9 };
|
||||
int nVersionRet = BuildVersionsFromRepo(
|
||||
lpRepoPath,
|
||||
@@ -613,7 +633,9 @@ int HandleSetVerCommand(int argc, TCHAR* argv[])
|
||||
strProductVersion,
|
||||
strFileVersion,
|
||||
DEFAULT_MAJOR_WHEN_NO_TAG,
|
||||
DEFAULT_MINOR_WHEN_NO_TAG);
|
||||
DEFAULT_MINOR_WHEN_NO_TAG,
|
||||
&strBranch,
|
||||
&stFileVersionDate);
|
||||
if (nVersionRet != 0)
|
||||
{
|
||||
return nVersionRet;
|
||||
@@ -633,6 +655,20 @@ int HandleSetVerCommand(int argc, TCHAR* argv[])
|
||||
_tprintf(_T("ProductVersion=%s\n"), strProductVersion.GetString());
|
||||
_tprintf(_T("FileVersion=%s\n"), strFileVersion.GetString());
|
||||
|
||||
CString strChangeLog;
|
||||
if (!TryGetGitLogByBranchDate(lpRepoPath, strBranch, stFileVersionDate, strChangeLog))
|
||||
{
|
||||
strChangeLog = _T("无当日提交记录");
|
||||
}
|
||||
|
||||
TCHAR szInfoPath[MAX_PATH] = { 0 };
|
||||
_tcscpy_s(szInfoPath, lpRepoPath);
|
||||
PathAppend(szInfoPath, _T("upload_info.txt"));
|
||||
if (!WriteUploadInfoVersions(szInfoPath, strProductVersion, strFileVersion, strChangeLog))
|
||||
{
|
||||
_tprintf(_T("警告: 未能写入 upload_info.txt\n"));
|
||||
}
|
||||
|
||||
return RewriteSetVerByProjectType(lpRepoPath, strProductVersion, strFileVersion);
|
||||
}
|
||||
|
||||
@@ -909,6 +945,15 @@ int main(int argc, TCHAR* argv[], TCHAR* envp[])
|
||||
return nCmdRet;
|
||||
}
|
||||
|
||||
if (argc >= 2 && _tcsicmp(argv[1], _T("upload")) == 0)
|
||||
{
|
||||
DWORD dwStartTick = LogCommandStart(_T("upload"));
|
||||
int nCmdRet = HandleUploadCommand(argc, argv);
|
||||
LogCommandEnd(_T("upload"), dwStartTick, nCmdRet);
|
||||
PrintCommandFailedWithCode(_T("upload"), nCmdRet);
|
||||
return nCmdRet;
|
||||
}
|
||||
|
||||
_tprintf(_T("错误:未知命令:%s\n"), argv[1]);
|
||||
PrintShortCommandUsage();
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<OutDir>..\..\..\..\bin\$(ProjectName)\</OutDir>
|
||||
<IntDir>$(OutDir)$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)d</TargetName>
|
||||
<TargetName>$(ProjectName)</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<OutDir>..\..\..\..\bin\$(ProjectName)\</OutDir>
|
||||
@@ -153,9 +153,11 @@
|
||||
<ClInclude Include="GitVer_process.h" />
|
||||
<ClInclude Include="GitVer_rewrite.h" />
|
||||
<ClInclude Include="GitVer_setup.h" />
|
||||
<ClInclude Include="GitVer_upload.h" />
|
||||
<ClInclude Include="GitVer_tag.h" />
|
||||
<ClInclude Include="GitVer_types.h" />
|
||||
<ClInclude Include="GitVer_version.h" />
|
||||
<ClInclude Include="httplib.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="Resource.h" />
|
||||
<ClInclude Include="targetver.h" />
|
||||
@@ -169,6 +171,7 @@
|
||||
<ClCompile Include="GitVer_process.cpp" />
|
||||
<ClCompile Include="GitVer_rewrite.cpp" />
|
||||
<ClCompile Include="GitVer_setup.cpp" />
|
||||
<ClCompile Include="GitVer_upload.cpp" />
|
||||
<ClCompile Include="GitVer_tag.cpp" />
|
||||
<ClCompile Include="GitVer_version.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
|
||||
@@ -60,6 +60,12 @@
|
||||
<ClInclude Include="GitVer_setup.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GitVer_upload.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="httplib.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="GitVer.cpp">
|
||||
@@ -95,6 +101,9 @@
|
||||
<ClCompile Include="GitVer_setup.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GitVer_upload.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="GitVer.rc">
|
||||
|
||||
@@ -69,9 +69,60 @@ BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector<std::string>& vtOldContent,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory)
|
||||
static CString DecodeProcessOutput(const std::string& strRawOutput, UINT nOutputCodePage)
|
||||
{
|
||||
CString strValue = _T("");
|
||||
if (strRawOutput.empty())
|
||||
{
|
||||
return CString();
|
||||
}
|
||||
|
||||
if (nOutputCodePage == CP_UTF8)
|
||||
{
|
||||
int nWideSize = MultiByteToWideChar(CP_UTF8, 0, strRawOutput.data(), (int)strRawOutput.size(), NULL, 0);
|
||||
if (nWideSize <= 0)
|
||||
{
|
||||
return CString();
|
||||
}
|
||||
|
||||
CStringW strWide;
|
||||
MultiByteToWideChar(CP_UTF8, 0, strRawOutput.data(), (int)strRawOutput.size(), strWide.GetBuffer(nWideSize), nWideSize);
|
||||
strWide.ReleaseBuffer(nWideSize);
|
||||
|
||||
#ifdef UNICODE
|
||||
return CString(strWide);
|
||||
#else
|
||||
int nGbkSize = WideCharToMultiByte(CP_ACP, 0, strWide, strWide.GetLength(), NULL, 0, NULL, NULL);
|
||||
if (nGbkSize <= 0)
|
||||
{
|
||||
return CString();
|
||||
}
|
||||
|
||||
std::string strGbk;
|
||||
strGbk.resize(nGbkSize);
|
||||
WideCharToMultiByte(CP_ACP, 0, strWide, strWide.GetLength(), &strGbk[0], nGbkSize, NULL, NULL);
|
||||
return CString(strGbk.c_str(), (int)strGbk.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef UNICODE
|
||||
int nSize = MultiByteToWideChar(nOutputCodePage, 0, strRawOutput.data(), (int)strRawOutput.size(), NULL, 0);
|
||||
if (nSize <= 0)
|
||||
{
|
||||
return CString();
|
||||
}
|
||||
|
||||
CString strValue;
|
||||
MultiByteToWideChar(nOutputCodePage, 0, strRawOutput.data(), (int)strRawOutput.size(), strValue.GetBuffer(nSize), nSize);
|
||||
strValue.ReleaseBuffer(nSize);
|
||||
return strValue;
|
||||
#else
|
||||
return CString(strRawOutput.c_str(), (int)strRawOutput.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory, UINT nOutputCodePage)
|
||||
{
|
||||
std::string strRawOutput;
|
||||
const int MY_PIPE_BUFFER_SIZE = 8912;
|
||||
CString strMutableArgs = args == NULL ? _T("") : args;
|
||||
LPTSTR lpArgs = strMutableArgs.IsEmpty() ? NULL : strMutableArgs.GetBuffer();
|
||||
@@ -128,12 +179,7 @@ CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory)
|
||||
break;
|
||||
}
|
||||
|
||||
szPipeOut[dwStdLen] = '\0';
|
||||
#ifdef UNICODE
|
||||
strValue.AppendFormat(_T("%hs"), szPipeOut);
|
||||
#else
|
||||
strValue += szPipeOut;
|
||||
#endif
|
||||
strRawOutput.append(szPipeOut, dwStdLen);
|
||||
}
|
||||
|
||||
WaitForSingleObject(processInfo.hProcess, INFINITE);
|
||||
@@ -160,6 +206,8 @@ CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory)
|
||||
strMutableArgs.ReleaseBuffer();
|
||||
}
|
||||
|
||||
CString strValue = DecodeProcessOutput(strRawOutput, nOutputCodePage);
|
||||
|
||||
_tprintf(_T("StartProcess program=%s args=%s cwd=%s output=%s\n"),
|
||||
program == NULL ? _T("<null>") : program,
|
||||
args == NULL ? _T("<null>") : args,
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
BOOL ReplaceFileContent(LPCTSTR lpFile, std::vector<std::string>& vtOldContent, std::vector<std::string>& vtNewContent);
|
||||
CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory);
|
||||
/// <summary>
|
||||
/// 启动子进程并捕获 stdout/stderr。
|
||||
/// nOutputCodePage 为管道原始字节的解码代码页,默认 CP_ACP(按系统 ANSI/GBK 直接使用);
|
||||
/// CP_UTF8 时先按 UTF-8 解码为宽字符,MultiByte 工程再转为 GBK 存入 CString。
|
||||
/// </summary>
|
||||
CString StartProcess(LPCTSTR program, LPCTSTR args, LPCTSTR lpCurrentDirectory, UINT nOutputCodePage = CP_ACP);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "gitver_cli.h"
|
||||
#include "gitver_version.h"
|
||||
#include "gitver_process.h"
|
||||
#include "gitver_upload.h"
|
||||
|
||||
extern TCHAR g_szCurModuleDir[MAX_PATH];
|
||||
|
||||
@@ -344,13 +345,143 @@ static BOOL ReadVersionFromProjectFiles(LPCTSTR lpBaseDir, CString& strVersion)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// 解析编译输出中的安装包路径
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
static CString ExtractQuotedPath(const CString& strText)
|
||||
{
|
||||
int nStart = strText.Find(_T('"'));
|
||||
if (nStart < 0)
|
||||
{
|
||||
return strText;
|
||||
}
|
||||
|
||||
int nEnd = strText.Find(_T('"'), nStart + 1);
|
||||
if (nEnd <= nStart)
|
||||
{
|
||||
return strText;
|
||||
}
|
||||
|
||||
return strText.Mid(nStart + 1, nEnd - nStart - 1);
|
||||
}
|
||||
|
||||
static BOOL FindNewestExeInDirectory(LPCTSTR lpDir, CString& strExePath)
|
||||
{
|
||||
strExePath.Empty();
|
||||
if (lpDir == NULL || lpDir[0] == _T('\0') || !PathIsDirectory(lpDir))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
CString strPattern;
|
||||
strPattern.Format(_T("%s\\*.exe"), lpDir);
|
||||
|
||||
WIN32_FIND_DATA findData = { 0 };
|
||||
HANDLE hFind = ::FindFirstFile(strPattern, &findData);
|
||||
if (hFind == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
FILETIME ftNewest = { 0 };
|
||||
BOOL bFound = FALSE;
|
||||
|
||||
do
|
||||
{
|
||||
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!bFound || CompareFileTime(&findData.ftLastWriteTime, &ftNewest) > 0)
|
||||
{
|
||||
ftNewest = findData.ftLastWriteTime;
|
||||
strExePath.Format(_T("%s\\%s"), lpDir, findData.cFileName);
|
||||
bFound = TRUE;
|
||||
}
|
||||
} while (::FindNextFile(hFind, &findData));
|
||||
|
||||
::FindClose(hFind);
|
||||
return bFound && PathFileExists(strExePath);
|
||||
}
|
||||
|
||||
static BOOL ParseInstallerPathFromBuildOutput(int nSetupType, const CString& strOutput, const CString& strScriptDir, CString& strInstallerPath)
|
||||
{
|
||||
strInstallerPath.Empty();
|
||||
|
||||
if (nSetupType == 0)
|
||||
{
|
||||
LPCTSTR lpMarker = _T("Resulting Setup program filename is:");
|
||||
int nPos = strOutput.Find(lpMarker);
|
||||
if (nPos >= 0)
|
||||
{
|
||||
CString strRest = strOutput.Mid(nPos + (int)_tcslen(lpMarker));
|
||||
strRest.Trim();
|
||||
|
||||
int nLineBreak = strRest.Find(_T('\n'));
|
||||
CString strFirst = (nLineBreak >= 0) ? strRest.Left(nLineBreak) : strRest;
|
||||
strFirst.Trim();
|
||||
if (!strFirst.IsEmpty())
|
||||
{
|
||||
strFirst = ExtractQuotedPath(strFirst);
|
||||
if (PathFileExists(strFirst))
|
||||
{
|
||||
strInstallerPath = strFirst;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (nLineBreak >= 0)
|
||||
{
|
||||
CString strSecond = strRest.Mid(nLineBreak + 1);
|
||||
strSecond.Trim();
|
||||
strSecond = ExtractQuotedPath(strSecond);
|
||||
if (PathFileExists(strSecond))
|
||||
{
|
||||
strInstallerPath = strSecond;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int nPos = 0;
|
||||
while ((nPos = strOutput.Find(_T("Output:"), nPos)) >= 0)
|
||||
{
|
||||
CString strRest = strOutput.Mid(nPos + 7);
|
||||
int nLineBreak = strRest.Find(_T('\n'));
|
||||
CString strLine = (nLineBreak >= 0) ? strRest.Left(nLineBreak) : strRest;
|
||||
strLine.Trim();
|
||||
strLine = ExtractQuotedPath(strLine);
|
||||
if (!strLine.IsEmpty() && PathFileExists(strLine))
|
||||
{
|
||||
strInstallerPath = strLine;
|
||||
return TRUE;
|
||||
}
|
||||
nPos += 7;
|
||||
}
|
||||
}
|
||||
|
||||
CString strOutputDir;
|
||||
strOutputDir.Format(_T("%s\\Output"), strScriptDir.GetString());
|
||||
if (FindNewestExeInDirectory(strOutputDir, strInstallerPath))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FindNewestExeInDirectory(strScriptDir, strInstallerPath);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// 公共执行函数
|
||||
// 不回写版本信息到项目文件,只修改安装脚本并调用编译器打包。
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
BOOL ExecuteSetupBuild(int nSetupType, const CString& strProductVersion, const CString& strFileVersion)
|
||||
BOOL ExecuteSetupBuild(int nSetupType, const CString& strProductVersion, const CString& strFileVersion, CString& strInstallerPath)
|
||||
{
|
||||
strInstallerPath.Empty();
|
||||
#ifdef _DEBUG
|
||||
Sleep(1500);
|
||||
#endif
|
||||
@@ -412,6 +543,14 @@ BOOL ExecuteSetupBuild(int nSetupType, const CString& strProductVersion, const C
|
||||
{
|
||||
_tprintf(_T("警告: 编译器无输出,可能执行失败。\n"));
|
||||
}
|
||||
else if (!ParseInstallerPathFromBuildOutput(nSetupType, strOutput, strScriptDir, strInstallerPath))
|
||||
{
|
||||
_tprintf(_T("警告: 未能从编译输出中识别安装包路径。\n"));
|
||||
}
|
||||
else
|
||||
{
|
||||
_tprintf(_T("安装包路径: %s\n"), strInstallerPath.GetString());
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@@ -538,10 +677,22 @@ int HandleSetupCommand(int argc, TCHAR* argv[])
|
||||
}
|
||||
|
||||
// 修改安装脚本并编译打包
|
||||
if (!ExecuteSetupBuild((int)nSetupType, strProductVersion, strFileVersion))
|
||||
CString strInstallerPath;
|
||||
if (!ExecuteSetupBuild((int)nSetupType, strProductVersion, strFileVersion, strInstallerPath))
|
||||
{
|
||||
return 38;
|
||||
}
|
||||
|
||||
if (!strInstallerPath.IsEmpty())
|
||||
{
|
||||
TCHAR szInfoPath[MAX_PATH] = { 0 };
|
||||
_tcscpy_s(szInfoPath, lpRepoPath);
|
||||
PathAppend(szInfoPath, _T("upload_info.txt"));
|
||||
if (!WriteUploadInfoPackageFile(szInfoPath, strInstallerPath))
|
||||
{
|
||||
_tprintf(_T("警告: 未能写入 upload_info.txt\n"));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
/// <summary>
|
||||
/// 查找安装脚本、修改版本号并调用编译器打包。
|
||||
/// nSetupType=0: Inno Setup (.iss);nSetupType=1: NSIS (.nsi)。
|
||||
/// 不写回项目版本文件(.rc / AssemblyInfo.cs)。
|
||||
/// 查找安装脚本、修改版本号并调用编译器打包。
|
||||
/// nSetupType=0: Inno Setup (.iss);nSetupType=1: NSIS (.nsi)。
|
||||
/// 不写回项目版本文件(.rc / AssemblyInfo.cs)。
|
||||
/// </summary>
|
||||
/// <returns>TRUE 成功;FALSE 失败(错误已打印)。</returns>
|
||||
BOOL ExecuteSetupBuild(int nSetupType, const CString& strProductVersion, const CString& strFileVersion);
|
||||
/// <returns>TRUE 成功;FALSE 失败(错误已打印)。</returns>
|
||||
BOOL ExecuteSetupBuild(int nSetupType, const CString& strProductVersion, const CString& strFileVersion, CString& strInstallerPath);
|
||||
|
||||
/// <summary>
|
||||
/// 处理独立的 setup=n 命令行入口。
|
||||
/// 退出码:0 成功,36 setup 值不支持,37 未找到脚本,
|
||||
/// 38 修改脚本失败,39 未找到编译器,其他同通用错误码。
|
||||
/// 处理独立的 setup=n 命令行入口。
|
||||
/// 退出码:0 成功,36 setup 值不支持,37 未找到脚本,
|
||||
/// 38 修改脚本失败,39 未找到编译器,其他同通用错误码。
|
||||
/// </summary>
|
||||
int HandleSetupCommand(int argc, TCHAR* argv[]);
|
||||
|
||||
929
GitVer/GitVer_upload.cpp
Normal file
929
GitVer/GitVer_upload.cpp
Normal file
@@ -0,0 +1,929 @@
|
||||
#include "pch.h"
|
||||
#include "GitVer_upload.h"
|
||||
|
||||
#include "httplib.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
extern std::string ToAnsiString(LPCTSTR lpText);
|
||||
extern BOOL ReadTextFileAsAnsi(LPCTSTR lpFile, std::string& strContent, std::string& strBomPrefix);
|
||||
|
||||
static const LPCTSTR DEFAULT_BASE_URL = _T("http://tools-center.mokadisplay.com/");
|
||||
|
||||
struct UploadInfoConfig
|
||||
{
|
||||
CString strBaseUrl;
|
||||
CString strPackageFile;
|
||||
CString strPackageName;
|
||||
CString strPackageTypeId;
|
||||
CString strProductVersion;
|
||||
CString strFileVersion;
|
||||
CString strFileChangeLog;
|
||||
CString strProductChangeLog;
|
||||
CString strDescription;
|
||||
CString strWikiUrl;
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// 字符串 / 编码工具
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
static std::string ToUtf8String(LPCTSTR lpText)
|
||||
{
|
||||
if (lpText == NULL || lpText[0] == _T('\0'))
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
|
||||
#ifdef UNICODE
|
||||
int nSize = WideCharToMultiByte(CP_UTF8, 0, lpText, -1, NULL, 0, NULL, NULL);
|
||||
if (nSize <= 1)
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::string strValue;
|
||||
strValue.resize(nSize - 1);
|
||||
WideCharToMultiByte(CP_UTF8, 0, lpText, -1, &strValue[0], nSize, NULL, NULL);
|
||||
return strValue;
|
||||
#else
|
||||
return std::string(lpText);
|
||||
#endif
|
||||
}
|
||||
|
||||
static CString Utf8ToCString(const std::string& strUtf8)
|
||||
{
|
||||
if (strUtf8.empty())
|
||||
{
|
||||
return CString();
|
||||
}
|
||||
|
||||
#ifdef UNICODE
|
||||
int nSize = MultiByteToWideChar(CP_UTF8, 0, strUtf8.c_str(), (int)strUtf8.size(), NULL, 0);
|
||||
if (nSize <= 0)
|
||||
{
|
||||
return CString();
|
||||
}
|
||||
|
||||
CString strValue;
|
||||
MultiByteToWideChar(CP_UTF8, 0, strUtf8.c_str(), (int)strUtf8.size(), strValue.GetBuffer(nSize), nSize);
|
||||
strValue.ReleaseBuffer(nSize);
|
||||
return strValue;
|
||||
#else
|
||||
int nSize = MultiByteToWideChar(CP_UTF8, 0, strUtf8.c_str(), (int)strUtf8.size(), NULL, 0);
|
||||
if (nSize <= 0)
|
||||
{
|
||||
return CString();
|
||||
}
|
||||
|
||||
CStringW strWide;
|
||||
MultiByteToWideChar(CP_UTF8, 0, strUtf8.c_str(), (int)strUtf8.size(), strWide.GetBuffer(nSize), nSize);
|
||||
strWide.ReleaseBuffer(nSize);
|
||||
|
||||
nSize = WideCharToMultiByte(CP_ACP, 0, strWide, -1, NULL, 0, NULL, NULL);
|
||||
if (nSize <= 1)
|
||||
{
|
||||
return CString();
|
||||
}
|
||||
|
||||
CString strValue;
|
||||
WideCharToMultiByte(CP_ACP, 0, strWide, -1, strValue.GetBuffer(nSize), nSize, NULL, NULL);
|
||||
strValue.ReleaseBuffer();
|
||||
return strValue;
|
||||
#endif
|
||||
}
|
||||
|
||||
static std::string UrlEncodeUtf8(const std::string& strValue)
|
||||
{
|
||||
static const char* kHex = "0123456789ABCDEF";
|
||||
std::string strEncoded;
|
||||
strEncoded.reserve(strValue.size() * 3);
|
||||
|
||||
for (unsigned char ch : strValue)
|
||||
{
|
||||
if ((ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= '0' && ch <= '9') ||
|
||||
ch == '-' || ch == '_' || ch == '.' || ch == '~')
|
||||
{
|
||||
strEncoded.push_back((char)ch);
|
||||
}
|
||||
else if (ch == ' ')
|
||||
{
|
||||
strEncoded.push_back('+');
|
||||
}
|
||||
else
|
||||
{
|
||||
strEncoded.push_back('%');
|
||||
strEncoded.push_back(kHex[ch >> 4]);
|
||||
strEncoded.push_back(kHex[ch & 0x0F]);
|
||||
}
|
||||
}
|
||||
|
||||
return strEncoded;
|
||||
}
|
||||
|
||||
static std::string UnescapeInfoValue(const std::string& strValue)
|
||||
{
|
||||
std::string strResult;
|
||||
strResult.reserve(strValue.size());
|
||||
|
||||
for (size_t i = 0; i < strValue.size(); ++i)
|
||||
{
|
||||
if (strValue[i] == '\\' && i + 1 < strValue.size() && strValue[i + 1] == 'n')
|
||||
{
|
||||
strResult.push_back('\n');
|
||||
++i;
|
||||
}
|
||||
else
|
||||
{
|
||||
strResult.push_back(strValue[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return strResult;
|
||||
}
|
||||
|
||||
static std::string EscapeInfoValueForFile(const std::string& strValue)
|
||||
{
|
||||
std::string strResult;
|
||||
strResult.reserve(strValue.size());
|
||||
|
||||
for (size_t i = 0; i < strValue.size(); ++i)
|
||||
{
|
||||
char ch = strValue[i];
|
||||
if (ch == '\r')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (ch == '\n')
|
||||
{
|
||||
strResult += "\\n";
|
||||
continue;
|
||||
}
|
||||
if (ch == '\\')
|
||||
{
|
||||
strResult += "\\\\";
|
||||
continue;
|
||||
}
|
||||
strResult.push_back(ch);
|
||||
}
|
||||
|
||||
return strResult;
|
||||
}
|
||||
|
||||
static void TrimAnsiInPlace(std::string& strValue)
|
||||
{
|
||||
while (!strValue.empty() && (strValue.front() == ' ' || strValue.front() == '\t' || strValue.front() == '\r'))
|
||||
{
|
||||
strValue.erase(strValue.begin());
|
||||
}
|
||||
while (!strValue.empty() && (strValue.back() == ' ' || strValue.back() == '\t' || strValue.back() == '\r'))
|
||||
{
|
||||
strValue.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL ReadBinaryFile(LPCTSTR lpFile, std::vector<BYTE>& data)
|
||||
{
|
||||
data.clear();
|
||||
CFile myFile;
|
||||
CFileException fileExp;
|
||||
if (!myFile.Open(lpFile, CFile::modeRead | CFile::shareDenyWrite, &fileExp))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ULONGLONG ullLength = myFile.GetLength();
|
||||
if (ullLength == 0)
|
||||
{
|
||||
myFile.Close();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
data.resize((size_t)ullLength);
|
||||
myFile.Read(data.data(), (UINT)ullLength);
|
||||
myFile.Close();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static std::string ToLowerAscii(std::string strValue)
|
||||
{
|
||||
for (char& ch : strValue)
|
||||
{
|
||||
if (ch >= 'A' && ch <= 'Z')
|
||||
{
|
||||
ch = (char)(ch - 'A' + 'a');
|
||||
}
|
||||
}
|
||||
return strValue;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Cookie 管理(httplib 不自动维护 Session)
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
static void TrimStdStringInPlace(std::string& strValue)
|
||||
{
|
||||
while (!strValue.empty() && (strValue.front() == ' ' || strValue.front() == '\t'))
|
||||
{
|
||||
strValue.erase(strValue.begin());
|
||||
}
|
||||
while (!strValue.empty() && (strValue.back() == ' ' || strValue.back() == '\t'))
|
||||
{
|
||||
strValue.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
static void MergeSetCookieValue(const std::string& strHeaderValue, std::string& strCookies)
|
||||
{
|
||||
std::string strLine = strHeaderValue;
|
||||
TrimStdStringInPlace(strLine);
|
||||
if (strLine.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
size_t nSemi = strLine.find(';');
|
||||
std::string strPair = (nSemi != std::string::npos) ? strLine.substr(0, nSemi) : strLine;
|
||||
TrimStdStringInPlace(strPair);
|
||||
if (strPair.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
size_t nEq = strPair.find('=');
|
||||
if (nEq == std::string::npos || nEq == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string strName = strPair.substr(0, nEq);
|
||||
std::string strValue = strPair.substr(nEq + 1);
|
||||
TrimStdStringInPlace(strName);
|
||||
TrimStdStringInPlace(strValue);
|
||||
if (strName.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string strNewPair = strName + "=" + strValue;
|
||||
if (strCookies.empty())
|
||||
{
|
||||
strCookies = strNewPair;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string strResult;
|
||||
BOOL bReplaced = FALSE;
|
||||
size_t nStart = 0;
|
||||
while (nStart < strCookies.size())
|
||||
{
|
||||
size_t nSemiPos = strCookies.find(';', nStart);
|
||||
std::string strItem = (nSemiPos != std::string::npos)
|
||||
? strCookies.substr(nStart, nSemiPos - nStart)
|
||||
: strCookies.substr(nStart);
|
||||
TrimStdStringInPlace(strItem);
|
||||
|
||||
if (!strItem.empty())
|
||||
{
|
||||
size_t nItemEq = strItem.find('=');
|
||||
std::string strItemName = (nItemEq != std::string::npos) ? strItem.substr(0, nItemEq) : strItem;
|
||||
TrimStdStringInPlace(strItemName);
|
||||
|
||||
if (!strResult.empty())
|
||||
{
|
||||
strResult += "; ";
|
||||
}
|
||||
|
||||
if (ToLowerAscii(strItemName) == ToLowerAscii(strName))
|
||||
{
|
||||
strResult += strNewPair;
|
||||
bReplaced = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
strResult += strItem;
|
||||
}
|
||||
}
|
||||
|
||||
if (nSemiPos == std::string::npos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
nStart = nSemiPos + 1;
|
||||
}
|
||||
|
||||
if (!bReplaced)
|
||||
{
|
||||
if (!strResult.empty())
|
||||
{
|
||||
strResult += "; ";
|
||||
}
|
||||
strResult += strNewPair;
|
||||
}
|
||||
|
||||
strCookies = strResult;
|
||||
}
|
||||
|
||||
static void CollectResponseCookies(const httplib::Response& response, std::string& strCookies)
|
||||
{
|
||||
for (const auto& header : response.headers)
|
||||
{
|
||||
if (ToLowerAscii(header.first) == "set-cookie")
|
||||
{
|
||||
MergeSetCookieValue(header.second, strCookies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static httplib::Headers MakeCookieHeaders(const std::string& strCookies)
|
||||
{
|
||||
httplib::Headers headers;
|
||||
if (!strCookies.empty())
|
||||
{
|
||||
headers.emplace("Cookie", strCookies);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
static std::unique_ptr<httplib::Client> CreatePmcClient(LPCTSTR lpBaseUrl)
|
||||
{
|
||||
std::string strBaseUrl = ToUtf8String(lpBaseUrl);
|
||||
auto cli = std::make_unique<httplib::Client>(strBaseUrl);
|
||||
if (!cli->is_valid())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cli->set_follow_location(false);
|
||||
cli->set_connection_timeout(30, 0);
|
||||
cli->set_read_timeout(300, 0);
|
||||
cli->set_write_timeout(300, 0);
|
||||
return cli;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// info 文件解析
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
static void ApplyInfoKeyValue(UploadInfoConfig& config, const std::string& strKey, const std::string& strRawValue)
|
||||
{
|
||||
std::string strValue = UnescapeInfoValue(strRawValue);
|
||||
CString strCStringValue = Utf8ToCString(strValue);
|
||||
|
||||
if (strKey == "url")
|
||||
{
|
||||
config.strBaseUrl = strCStringValue;
|
||||
}
|
||||
else if (strKey == "file")
|
||||
{
|
||||
config.strPackageFile = strCStringValue;
|
||||
}
|
||||
else if (strKey == "package_name")
|
||||
{
|
||||
config.strPackageName = strCStringValue;
|
||||
}
|
||||
else if (strKey == "package_type_id")
|
||||
{
|
||||
config.strPackageTypeId = strCStringValue;
|
||||
}
|
||||
else if (strKey == "product_version")
|
||||
{
|
||||
config.strProductVersion = strCStringValue;
|
||||
}
|
||||
else if (strKey == "file_version")
|
||||
{
|
||||
config.strFileVersion = strCStringValue;
|
||||
}
|
||||
else if (strKey == "file_change_log")
|
||||
{
|
||||
config.strFileChangeLog = strCStringValue;
|
||||
}
|
||||
else if (strKey == "product_change_log")
|
||||
{
|
||||
config.strProductChangeLog = strCStringValue;
|
||||
}
|
||||
else if (strKey == "description")
|
||||
{
|
||||
config.strDescription = strCStringValue;
|
||||
}
|
||||
else if (strKey == "wiki_url")
|
||||
{
|
||||
config.strWikiUrl = strCStringValue;
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL LoadUploadInfoFile(LPCTSTR lpInfoFile, UploadInfoConfig& config)
|
||||
{
|
||||
std::string strContent;
|
||||
std::string strBomPrefix;
|
||||
if (!ReadTextFileAsAnsi(lpInfoFile, strContent, strBomPrefix))
|
||||
{
|
||||
_tprintf(_T("错误: 无法读取 info 配置文件 %s\n"), lpInfoFile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
size_t nLineStart = 0;
|
||||
while (nLineStart <= strContent.size())
|
||||
{
|
||||
size_t nLineEnd = strContent.find('\n', nLineStart);
|
||||
if (nLineEnd == std::string::npos)
|
||||
{
|
||||
nLineEnd = strContent.size();
|
||||
}
|
||||
|
||||
std::string strLine = strContent.substr(nLineStart, nLineEnd - nLineStart);
|
||||
if (!strLine.empty() && strLine.back() == '\r')
|
||||
{
|
||||
strLine.pop_back();
|
||||
}
|
||||
TrimAnsiInPlace(strLine);
|
||||
|
||||
if (!strLine.empty() && strLine[0] != '#')
|
||||
{
|
||||
size_t nEq = strLine.find('=');
|
||||
if (nEq != std::string::npos)
|
||||
{
|
||||
std::string strKey = strLine.substr(0, nEq);
|
||||
std::string strValue = strLine.substr(nEq + 1);
|
||||
TrimAnsiInPlace(strKey);
|
||||
TrimAnsiInPlace(strValue);
|
||||
if (!strKey.empty())
|
||||
{
|
||||
ApplyInfoKeyValue(config, strKey, strValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nLineEnd >= strContent.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
nLineStart = nLineEnd + 1;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL ValidateUploadConfig(const UploadInfoConfig& config)
|
||||
{
|
||||
if (config.strPackageName.IsEmpty())
|
||||
{
|
||||
_tprintf(_T("错误: info 文件缺少 package_name\n"));
|
||||
return FALSE;
|
||||
}
|
||||
if (config.strPackageTypeId.IsEmpty())
|
||||
{
|
||||
_tprintf(_T("错误: info 文件缺少 package_type_id\n"));
|
||||
return FALSE;
|
||||
}
|
||||
if (config.strProductVersion.IsEmpty())
|
||||
{
|
||||
_tprintf(_T("错误: info 文件缺少 product_version\n"));
|
||||
return FALSE;
|
||||
}
|
||||
if (config.strFileVersion.IsEmpty())
|
||||
{
|
||||
_tprintf(_T("错误: info 文件缺少 file_version\n"));
|
||||
return FALSE;
|
||||
}
|
||||
if (config.strFileChangeLog.IsEmpty())
|
||||
{
|
||||
_tprintf(_T("错误: info 文件缺少 file_change_log\n"));
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL ResolvePasswordFromEnv(LPCTSTR lpUsername, CString& strPassword)
|
||||
{
|
||||
if (lpUsername == NULL || lpUsername[0] == _T('\0'))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#ifdef UNICODE
|
||||
LPWSTR lpEnvValue = _wgetenv(lpUsername);
|
||||
if (lpEnvValue == NULL || lpEnvValue[0] == L'\0')
|
||||
{
|
||||
_tprintf(_T("错误: 未设置环境变量 %s(用于读取登录密码)\n"), lpUsername);
|
||||
return FALSE;
|
||||
}
|
||||
strPassword = lpEnvValue;
|
||||
#else
|
||||
std::string strEnvName = ToAnsiString(lpUsername);
|
||||
char* lpBuffer = NULL;
|
||||
size_t nLen = 0;
|
||||
if (_dupenv_s(&lpBuffer, &nLen, strEnvName.c_str()) != 0 || lpBuffer == NULL || lpBuffer[0] == '\0')
|
||||
{
|
||||
if (lpBuffer != NULL)
|
||||
{
|
||||
free(lpBuffer);
|
||||
}
|
||||
_tprintf(_T("错误: 未设置环境变量 %s(用于读取登录密码)\n"), lpUsername);
|
||||
return FALSE;
|
||||
}
|
||||
strPassword = lpBuffer;
|
||||
free(lpBuffer);
|
||||
#endif
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL UpdateUploadInfoKeys(LPCTSTR lpInfoFile, const std::map<std::string, std::string>& updates)
|
||||
{
|
||||
std::vector<std::string> outLines;
|
||||
std::string strBomPrefix;
|
||||
std::map<std::string, BOOL> foundKeys;
|
||||
|
||||
for (const auto& item : updates)
|
||||
{
|
||||
foundKeys[item.first] = FALSE;
|
||||
}
|
||||
|
||||
if (PathFileExists(lpInfoFile))
|
||||
{
|
||||
std::string strContent;
|
||||
if (!ReadTextFileAsAnsi(lpInfoFile, strContent, strBomPrefix))
|
||||
{
|
||||
_tprintf(_T("警告: 无法读取 upload_info.txt,将创建新文件\n"));
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t nLineStart = 0;
|
||||
while (nLineStart <= strContent.size())
|
||||
{
|
||||
size_t nLineEnd = strContent.find('\n', nLineStart);
|
||||
if (nLineEnd == std::string::npos)
|
||||
{
|
||||
nLineEnd = strContent.size();
|
||||
}
|
||||
|
||||
std::string strLine = strContent.substr(nLineStart, nLineEnd - nLineStart);
|
||||
if (!strLine.empty() && strLine.back() == '\r')
|
||||
{
|
||||
strLine.pop_back();
|
||||
}
|
||||
|
||||
std::string strOutLine = strLine;
|
||||
if (!strLine.empty() && strLine[0] != '#')
|
||||
{
|
||||
size_t nEq = strLine.find('=');
|
||||
if (nEq != std::string::npos)
|
||||
{
|
||||
std::string strKey = strLine.substr(0, nEq);
|
||||
TrimAnsiInPlace(strKey);
|
||||
std::map<std::string, std::string>::const_iterator it = updates.find(strKey);
|
||||
if (it != updates.end())
|
||||
{
|
||||
strOutLine = strKey + "=" + it->second;
|
||||
foundKeys[strKey] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outLines.push_back(strOutLine);
|
||||
if (nLineEnd >= strContent.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
nLineStart = nLineEnd + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& item : updates)
|
||||
{
|
||||
if (!foundKeys[item.first])
|
||||
{
|
||||
outLines.push_back(item.first + "=" + item.second);
|
||||
}
|
||||
}
|
||||
|
||||
CFile myFile;
|
||||
CFileException fileExp;
|
||||
if (!myFile.Open(lpInfoFile, CFile::modeCreate | CFile::modeWrite, &fileExp))
|
||||
{
|
||||
_tprintf(_T("错误: 无法写入 upload_info.txt %s\n"), lpInfoFile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!strBomPrefix.empty())
|
||||
{
|
||||
myFile.Write(strBomPrefix.c_str(), (UINT)strBomPrefix.size());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < outLines.size(); ++i)
|
||||
{
|
||||
myFile.Write(outLines[i].c_str(), (UINT)outLines[i].size());
|
||||
myFile.Write("\n", 1);
|
||||
}
|
||||
myFile.Close();
|
||||
|
||||
_tprintf(_T("已更新 upload_info.txt: %s\n"), lpInfoFile);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL WriteUploadInfoVersions(
|
||||
LPCTSTR lpInfoFile,
|
||||
LPCTSTR lpProductVersion,
|
||||
LPCTSTR lpFileVersion,
|
||||
LPCTSTR lpFileChangeLog)
|
||||
{
|
||||
std::map<std::string, std::string> updates;
|
||||
updates["product_version"] = ToUtf8String(lpProductVersion);
|
||||
updates["file_version"] = ToUtf8String(lpFileVersion);
|
||||
updates["file_change_log"] = EscapeInfoValueForFile(ToUtf8String(lpFileChangeLog));
|
||||
return UpdateUploadInfoKeys(lpInfoFile, updates);
|
||||
}
|
||||
|
||||
BOOL WriteUploadInfoPackageFile(LPCTSTR lpInfoFile, LPCTSTR lpPackageFile)
|
||||
{
|
||||
std::map<std::string, std::string> updates;
|
||||
updates["file"] = ToUtf8String(lpPackageFile);
|
||||
return UpdateUploadInfoKeys(lpInfoFile, updates);
|
||||
}
|
||||
|
||||
static void PrintUploadUsage()
|
||||
{
|
||||
_tprintf(_T("用法:gitver upload username=<用户名> [file=<上传的文件>] [info=<配置.txt>] [url=<PMC基址>]\n"));
|
||||
_tprintf(_T("说明:info= 缺省为 upload_info.txt;file= 可省略,从 info 的 file 字段读取\n"));
|
||||
_tprintf(_T("说明:登录密码从与 username 同名的环境变量读取\n"));
|
||||
_tprintf(_T("示例:gitver upload username=admin\n"));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// PMC 交互(cpp-httplib)
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
static int PmcLogin(httplib::Client& cli, LPCTSTR lpUsername, LPCTSTR lpPassword, std::string& strCookies)
|
||||
{
|
||||
strCookies.clear();
|
||||
httplib::Params params;
|
||||
params.emplace("username", ToUtf8String(lpUsername));
|
||||
params.emplace("password", ToUtf8String(lpPassword));
|
||||
|
||||
httplib::Result result = cli.Post("/auth/login", params);
|
||||
if (!result)
|
||||
{
|
||||
_tprintf(_T("错误: 登录请求失败(网络错误)\n"));
|
||||
return 53;
|
||||
}
|
||||
|
||||
if (result->status == 401 || result->status == 403)
|
||||
{
|
||||
_tprintf(_T("错误: 登录被拒绝(HTTP %d)\n"), result->status);
|
||||
return 50;
|
||||
}
|
||||
|
||||
if (result->status != 303)
|
||||
{
|
||||
_tprintf(_T("错误: 登录返回意外状态码 HTTP %d\n"), result->status);
|
||||
return 50;
|
||||
}
|
||||
|
||||
std::string strLocation = ToLowerAscii(result->get_header_value("Location"));
|
||||
if (strLocation.find("/login") != std::string::npos)
|
||||
{
|
||||
_tprintf(_T("错误: 登录失败,用户名或密码错误\n"));
|
||||
return 50;
|
||||
}
|
||||
|
||||
CollectResponseCookies(*result, strCookies);
|
||||
if (strCookies.empty())
|
||||
{
|
||||
_tprintf(_T("错误: 登录成功但未收到 Session Cookie\n"));
|
||||
return 50;
|
||||
}
|
||||
|
||||
_tprintf(_T("已登录 PMC 账号 %s\n"), lpUsername);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int PmcUploadPackage(httplib::Client& cli, const std::string& strCookies, const UploadInfoConfig& config, LPCTSTR lpFilePath)
|
||||
{
|
||||
std::vector<BYTE> fileData;
|
||||
if (!ReadBinaryFile(lpFilePath, fileData))
|
||||
{
|
||||
_tprintf(_T("错误: 读取上传文件失败 %s\n"), lpFilePath);
|
||||
return 48;
|
||||
}
|
||||
|
||||
std::string strFileContent((const char*)fileData.data(), fileData.size());
|
||||
std::string strFileName = ToUtf8String(PathFindFileName(lpFilePath));
|
||||
|
||||
httplib::UploadFormDataItems items;
|
||||
items.push_back({ "package_name", ToUtf8String(config.strPackageName), "", "" });
|
||||
items.push_back({ "package_type_id", ToUtf8String(config.strPackageTypeId), "", "" });
|
||||
items.push_back({ "description", ToUtf8String(config.strDescription), "", "" });
|
||||
items.push_back({ "wiki_url", ToUtf8String(config.strWikiUrl), "", "" });
|
||||
items.push_back({ "product_version", ToUtf8String(config.strProductVersion), "", "" });
|
||||
items.push_back({ "file_version", ToUtf8String(config.strFileVersion), "", "" });
|
||||
items.push_back({ "product_change_log", ToUtf8String(config.strProductChangeLog), "", "" });
|
||||
items.push_back({ "file_change_log", ToUtf8String(config.strFileChangeLog), "", "" });
|
||||
items.push_back({ "package_file", strFileContent, strFileName, "application/octet-stream" });
|
||||
|
||||
httplib::Result result = cli.Post("/admin/packages/upload", MakeCookieHeaders(strCookies), items);
|
||||
if (!result)
|
||||
{
|
||||
_tprintf(_T("错误: 上传请求失败(网络错误)\n"));
|
||||
return 53;
|
||||
}
|
||||
|
||||
if (result->status == 401)
|
||||
{
|
||||
_tprintf(_T("错误: 上传时未登录(HTTP 401)\n"));
|
||||
return 51;
|
||||
}
|
||||
if (result->status == 403)
|
||||
{
|
||||
_tprintf(_T("错误: 上传需要管理员权限(HTTP 403)\n"));
|
||||
return 51;
|
||||
}
|
||||
if (result->status != 303)
|
||||
{
|
||||
_tprintf(_T("错误: 上传返回意外状态码 HTTP %d\n"), result->status);
|
||||
return 51;
|
||||
}
|
||||
|
||||
CString strLocation = Utf8ToCString(result->get_header_value("Location"));
|
||||
_tprintf(_T("已提交上传请求,HTTP 303 -> %s\n"), strLocation.GetString());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static BOOL JsonContainsFieldValue(const std::string& strBody, LPCSTR lpField, LPCSTR lpValue)
|
||||
{
|
||||
if (lpField == NULL || lpValue == NULL)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::string strNeedle = std::string("\"") + lpField + "\":\"" + lpValue + "\"";
|
||||
return strBody.find(strNeedle) != std::string::npos;
|
||||
}
|
||||
|
||||
static int VerifyUploadResult(httplib::Client& cli, const std::string& strCookies, const UploadInfoConfig& config)
|
||||
{
|
||||
std::string strPath = "/api/packages/latest?keyword=" +
|
||||
UrlEncodeUtf8(ToUtf8String(config.strPackageName)) + "&page_size=50";
|
||||
|
||||
httplib::Result result = cli.Get(strPath, MakeCookieHeaders(strCookies));
|
||||
if (!result)
|
||||
{
|
||||
_tprintf(_T("错误: 验证上传结果时网络请求失败\n"));
|
||||
return 53;
|
||||
}
|
||||
|
||||
if (result->status != 200)
|
||||
{
|
||||
_tprintf(_T("错误: 验证上传结果返回 HTTP %d\n"), result->status);
|
||||
return 52;
|
||||
}
|
||||
|
||||
std::string strPackageName = ToUtf8String(config.strPackageName);
|
||||
std::string strProductVersion = ToUtf8String(config.strProductVersion);
|
||||
std::string strFileVersion = ToUtf8String(config.strFileVersion);
|
||||
|
||||
if (JsonContainsFieldValue(result->body, "package_name", strPackageName.c_str()) &&
|
||||
JsonContainsFieldValue(result->body, "product_version", strProductVersion.c_str()) &&
|
||||
JsonContainsFieldValue(result->body, "file_version", strFileVersion.c_str()))
|
||||
{
|
||||
_tprintf(_T("upload ok: %s product_version="), config.strPackageName.GetString());
|
||||
_tprintf(_T("%s file_version="), config.strProductVersion.GetString());
|
||||
_tprintf(_T("%s\n"), config.strFileVersion.GetString());
|
||||
return 0;
|
||||
}
|
||||
|
||||
_tprintf(_T("错误: 上传后未在 PMC 中找到对应版本,可能表单校验失败或版本冲突\n"));
|
||||
return 52;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// 命令入口
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
int HandleUploadCommand(int argc, TCHAR* argv[])
|
||||
{
|
||||
CString strUsername;
|
||||
CString strFilePath;
|
||||
CString strInfoPath;
|
||||
CString strUrlOverride;
|
||||
|
||||
for (int i = 2; i < argc; ++i)
|
||||
{
|
||||
CString strArg = argv[i];
|
||||
if (strArg.GetLength() > 9 && strArg.Left(9).CompareNoCase(_T("username=")) == 0)
|
||||
{
|
||||
strUsername = strArg.Mid(9);
|
||||
continue;
|
||||
}
|
||||
if (strArg.GetLength() > 5 && strArg.Left(5).CompareNoCase(_T("file=")) == 0)
|
||||
{
|
||||
strFilePath = strArg.Mid(5);
|
||||
continue;
|
||||
}
|
||||
if (strArg.GetLength() > 5 && strArg.Left(5).CompareNoCase(_T("info=")) == 0)
|
||||
{
|
||||
strInfoPath = strArg.Mid(5);
|
||||
continue;
|
||||
}
|
||||
if (strArg.GetLength() > 4 && strArg.Left(4).CompareNoCase(_T("url=")) == 0)
|
||||
{
|
||||
strUrlOverride = strArg.Mid(4);
|
||||
continue;
|
||||
}
|
||||
|
||||
_tprintf(_T("错误: 多余的参数 %s\n"), argv[i]);
|
||||
PrintUploadUsage();
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (strUsername.IsEmpty())
|
||||
{
|
||||
_tprintf(_T("错误: upload 缺少必填参数 username=\n"));
|
||||
PrintUploadUsage();
|
||||
return 47;
|
||||
}
|
||||
|
||||
if (strInfoPath.IsEmpty())
|
||||
{
|
||||
strInfoPath = _T("upload_info.txt");
|
||||
}
|
||||
|
||||
if (!PathFileExists(strInfoPath))
|
||||
{
|
||||
_tprintf(_T("错误: info 配置文件不存在 %s\n"), strInfoPath.GetString());
|
||||
return 49;
|
||||
}
|
||||
|
||||
UploadInfoConfig config;
|
||||
config.strBaseUrl = DEFAULT_BASE_URL;
|
||||
|
||||
if (!LoadUploadInfoFile(strInfoPath, config))
|
||||
{
|
||||
return 49;
|
||||
}
|
||||
|
||||
if (strFilePath.IsEmpty())
|
||||
{
|
||||
strFilePath = config.strPackageFile;
|
||||
}
|
||||
|
||||
if (strFilePath.IsEmpty())
|
||||
{
|
||||
_tprintf(_T("错误: 未指定 file=,且 info 文件中缺少 file 字段\n"));
|
||||
PrintUploadUsage();
|
||||
return 47;
|
||||
}
|
||||
|
||||
if (!PathFileExists(strFilePath))
|
||||
{
|
||||
_tprintf(_T("错误: 上传文件不存在 %s\n"), strFilePath.GetString());
|
||||
return 48;
|
||||
}
|
||||
|
||||
if (!strUrlOverride.IsEmpty())
|
||||
{
|
||||
config.strBaseUrl = strUrlOverride;
|
||||
}
|
||||
if (config.strBaseUrl.IsEmpty())
|
||||
{
|
||||
config.strBaseUrl = DEFAULT_BASE_URL;
|
||||
}
|
||||
|
||||
if (!ValidateUploadConfig(config))
|
||||
{
|
||||
return 49;
|
||||
}
|
||||
|
||||
CString strPassword;
|
||||
if (!ResolvePasswordFromEnv(strUsername, strPassword))
|
||||
{
|
||||
return 49;
|
||||
}
|
||||
|
||||
std::unique_ptr<httplib::Client> cli = CreatePmcClient(config.strBaseUrl);
|
||||
if (!cli)
|
||||
{
|
||||
_tprintf(_T("错误: 无法连接 PMC 服务 %s\n"), config.strBaseUrl.GetString());
|
||||
return 53;
|
||||
}
|
||||
|
||||
std::string strCookies;
|
||||
int nLoginRet = PmcLogin(*cli, strUsername, strPassword, strCookies);
|
||||
if (nLoginRet != 0)
|
||||
{
|
||||
return nLoginRet;
|
||||
}
|
||||
|
||||
int nUploadRet = PmcUploadPackage(*cli, strCookies, config, strFilePath);
|
||||
if (nUploadRet != 0)
|
||||
{
|
||||
return nUploadRet;
|
||||
}
|
||||
|
||||
return VerifyUploadResult(*cli, strCookies, config);
|
||||
}
|
||||
22
GitVer/GitVer_upload.h
Normal file
22
GitVer/GitVer_upload.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
/// <summary>
|
||||
/// 处理 upload 命令:登录 PMC 并上传包文件。
|
||||
/// 退出码:0 成功,47 缺少参数,48 文件不存在,49 info 无效或未设置同名环境变量密码,
|
||||
/// 50 登录失败,51 上传 HTTP 失败,52 验证未找到版本,53 网络错误。
|
||||
/// </summary>
|
||||
int HandleUploadCommand(int argc, TCHAR* argv[]);
|
||||
|
||||
/// <summary>
|
||||
/// 更新 upload_info.txt 中的 product_version、file_version、file_change_log,保留其他字段。
|
||||
/// </summary>
|
||||
BOOL WriteUploadInfoVersions(
|
||||
LPCTSTR lpInfoFile,
|
||||
LPCTSTR lpProductVersion,
|
||||
LPCTSTR lpFileVersion,
|
||||
LPCTSTR lpFileChangeLog);
|
||||
|
||||
/// <summary>
|
||||
/// 更新 upload_info.txt 中的 file(安装包路径),保留其他字段。
|
||||
/// </summary>
|
||||
BOOL WriteUploadInfoPackageFile(LPCTSTR lpInfoFile, LPCTSTR lpPackageFile);
|
||||
@@ -313,7 +313,7 @@ BOOL TryGetTagCommitDate(LPCTSTR lpRepoPath, const CString& strTag, SYSTEMTIME&
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL ResolveTagFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString& strBranch, const CString& strTag, UINT& nYY, UINT& nMMDD, UINT& nId)
|
||||
BOOL ResolveTagFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString& strBranch, const CString& strTag, UINT& nYY, UINT& nMMDD, UINT& nId, SYSTEMTIME* pOutDate)
|
||||
{
|
||||
nYY = 0;
|
||||
nMMDD = 0;
|
||||
@@ -351,10 +351,14 @@ BOOL ResolveTagFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString&
|
||||
|
||||
GetFileVersionDateFromSystemTime(stTargetDate, nYY, nMMDD);
|
||||
nId = nCommitCount;
|
||||
if (pOutDate != NULL)
|
||||
{
|
||||
*pOutDate = stTargetDate;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL ResolveFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString& strBranch, UINT& nYY, UINT& nMMDD, UINT& nId)
|
||||
BOOL ResolveFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString& strBranch, UINT& nYY, UINT& nMMDD, UINT& nId, SYSTEMTIME* pOutDate)
|
||||
{
|
||||
nYY = 0;
|
||||
nMMDD = 0;
|
||||
@@ -396,6 +400,47 @@ BOOL ResolveFileVersionDateAndCommitCount(LPCTSTR lpRepoPath, const CString& str
|
||||
|
||||
GetFileVersionDateFromSystemTime(stTargetDate, nYY, nMMDD);
|
||||
nId = nTodayCommitCount;
|
||||
if (pOutDate != NULL)
|
||||
{
|
||||
*pOutDate = stTargetDate;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL TryGetGitLogByBranchDate(LPCTSTR lpRepoPath, const CString& strBranch, const SYSTEMTIME& stDate, CString& strLog)
|
||||
{
|
||||
strLog.Empty();
|
||||
if (strBranch.IsEmpty())
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
CString strCmd;
|
||||
strCmd.Format(
|
||||
_T("cmd /c git log --reverse --pretty=format:%%s --since=\"%04u-%02u-%02u 00:00:00\" --until=\"%04u-%02u-%02u 23:59:59\" \"origin/%s\""),
|
||||
stDate.wYear,
|
||||
stDate.wMonth,
|
||||
stDate.wDay,
|
||||
stDate.wYear,
|
||||
stDate.wMonth,
|
||||
stDate.wDay,
|
||||
strBranch.GetString());
|
||||
|
||||
CString strOutput = StartProcess(NULL, strCmd.GetBuffer(), lpRepoPath, CP_UTF8);
|
||||
strCmd.ReleaseBuffer();
|
||||
if (strOutput.IsEmpty())
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
strOutput.Replace(_T("\r"), _T(""));
|
||||
strOutput.Trim();
|
||||
if (strOutput.IsEmpty() || strOutput.Left(6).CompareNoCase(_T("fatal:")) == 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
strLog = strOutput;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -406,7 +451,9 @@ int BuildVersionsFromRepo(
|
||||
CString& strProductVersion,
|
||||
CString& strFileVersion,
|
||||
UINT nDefaultMajor,
|
||||
UINT nDefaultMinor)
|
||||
UINT nDefaultMinor,
|
||||
CString* pOutBranch,
|
||||
SYSTEMTIME* pOutFileVersionDate)
|
||||
{
|
||||
_tprintf(_T("开始构建版本: 仓库路径=%s pid=%u 默认Tag(%u.%u)\n"),
|
||||
lpRepoPath == NULL ? _T("<null>") : lpRepoPath,
|
||||
@@ -441,12 +488,17 @@ int BuildVersionsFromRepo(
|
||||
_tprintf(_T("游离标签版本: %s (major=%u minor=%u)\n"), strTag.GetString(), nMajor, nMinor);
|
||||
}
|
||||
|
||||
if (!ResolveTagFileVersionDateAndCommitCount(lpRepoPath, strBranch, strTag, nYY, nMMDD, nId))
|
||||
if (!ResolveTagFileVersionDateAndCommitCount(lpRepoPath, strBranch, strTag, nYY, nMMDD, nId, pOutFileVersionDate))
|
||||
{
|
||||
_tprintf(_T("错误: 获取游离标签提交信息失败\n"));
|
||||
return errorCodes.nCommitErrorCode;
|
||||
}
|
||||
|
||||
if (pOutBranch != NULL)
|
||||
{
|
||||
*pOutBranch = strBranch;
|
||||
}
|
||||
|
||||
_tprintf(_T("游离标签提交信息: yy=%u mmdd=%u id=%u\n"), nYY, nMMDD, nId);
|
||||
}
|
||||
else
|
||||
@@ -477,11 +529,17 @@ int BuildVersionsFromRepo(
|
||||
_tprintf(_T("最近标签: %s (major=%u minor=%u)\n"), strLastTag.GetString(), nMajor, nMinor);
|
||||
}
|
||||
|
||||
if (!ResolveFileVersionDateAndCommitCount(lpRepoPath, strBranch, nYY, nMMDD, nId))
|
||||
if (!ResolveFileVersionDateAndCommitCount(lpRepoPath, strBranch, nYY, nMMDD, nId, pOutFileVersionDate))
|
||||
{
|
||||
_tprintf(_T("错误: 获取分支提交信息失败\n"));
|
||||
return errorCodes.nCommitErrorCode;
|
||||
}
|
||||
|
||||
if (pOutBranch != NULL)
|
||||
{
|
||||
*pOutBranch = strBranch;
|
||||
}
|
||||
|
||||
_tprintf(_T("分支提交信息: yy=%u mmdd=%u id=%u\n"), nYY, nMMDD, nId);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,20 @@ BOOL ResolveTagFileVersionDateAndCommitCount(
|
||||
const CString& strTag,
|
||||
UINT& nYY,
|
||||
UINT& nMMDD,
|
||||
UINT& nId);
|
||||
UINT& nId,
|
||||
SYSTEMTIME* pOutDate = NULL);
|
||||
BOOL ResolveFileVersionDateAndCommitCount(
|
||||
LPCTSTR lpRepoPath,
|
||||
const CString& strBranch,
|
||||
UINT& nYY,
|
||||
UINT& nMMDD,
|
||||
UINT& nId,
|
||||
SYSTEMTIME* pOutDate = NULL);
|
||||
BOOL TryGetGitLogByBranchDate(
|
||||
LPCTSTR lpRepoPath,
|
||||
const CString& strBranch,
|
||||
const SYSTEMTIME& stDate,
|
||||
CString& strLog);
|
||||
int BuildVersionsFromRepo(
|
||||
LPCTSTR lpRepoPath,
|
||||
UINT nPid,
|
||||
@@ -29,4 +42,6 @@ int BuildVersionsFromRepo(
|
||||
CString& strProductVersion,
|
||||
CString& strFileVersion,
|
||||
UINT nDefaultMajor,
|
||||
UINT nDefaultMinor);
|
||||
UINT nDefaultMinor,
|
||||
CString* pOutBranch = NULL,
|
||||
SYSTEMTIME* pOutFileVersionDate = NULL);
|
||||
|
||||
20834
GitVer/httplib.h
Normal file
20834
GitVer/httplib.h
Normal file
File diff suppressed because it is too large
Load Diff
117
README.md
117
README.md
@@ -15,6 +15,7 @@ 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]
|
||||
gitver upload username=<用户名> [file=<上传文件>] [info=<配置.txt>] [url=<PMC基址>]
|
||||
```
|
||||
|
||||
**通用参数说明:**
|
||||
@@ -116,8 +117,21 @@ gitver setver=5 -test
|
||||
```
|
||||
ProductVersion=5.0.1920.11
|
||||
FileVersion=5.26.0519.3
|
||||
已更新 upload_info.txt: E:\Code\MyProj\upload_info.txt
|
||||
```
|
||||
|
||||
**upload_info.txt 自动更新:**
|
||||
|
||||
`setver` 成功生成版本号后,会在仓库目录(`repodir`,缺省为当前目录)写入或更新 `upload_info.txt`:
|
||||
|
||||
| 字段 | 来源 |
|
||||
|---|---|
|
||||
| `product_version` | 本次生成的 ProductVersion |
|
||||
| `file_version` | 本次生成的 FileVersion |
|
||||
| `file_change_log` | 文件版本对应日期的 git 提交说明(`origin/<分支>` 当天 log,多条以换行分隔) |
|
||||
|
||||
`setup` 成功编译安装包后,还会写入 `file`(安装包路径)。文件中已有的其他字段(如 `package_name`、`package_type_id`、`url` 等)会保留不变。
|
||||
|
||||
**回写说明:**
|
||||
|
||||
- **C++**:回写 `.rc` 文件中的 FILEVERSION、PRODUCTVERSION 及对应字符串字段
|
||||
@@ -265,6 +279,81 @@ gitver setup=0 pid=5
|
||||
gitver setup=1 pid=5 repodir=E:\Code\MyProj
|
||||
```
|
||||
|
||||
**upload_info.txt 自动更新:**
|
||||
|
||||
`setup` 编译成功后,会在仓库目录(`repodir`,缺省为当前目录)写入或更新 `upload_info.txt` 的 `file` 字段(安装包完整路径)。路径从 ISCC/NSIS 编译输出中解析;若无法解析,则尝试脚本目录下 `Output\*.exe` 或脚本目录内最新 `.exe`。
|
||||
|
||||
**输出示例:**
|
||||
|
||||
```
|
||||
安装包路径: E:\Code\MyProj\Output\MyApp_Setup_5.26.0624.3.exe
|
||||
已更新 upload_info.txt: E:\Code\MyProj\upload_info.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `gitver upload`(上传包到 PMC)
|
||||
|
||||
将构建产物上传到 PMC 包管理中心。需 PMC 服务已运行,且账号具有管理员权限。
|
||||
|
||||
```
|
||||
gitver upload username=<用户名> [file=<上传文件>] [info=<配置.txt>] [url=<PMC基址>]
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
|---|---|
|
||||
| `username=` | PMC 登录账号(必填);登录密码从**同名环境变量**读取,如 `username=admin` 读取 `%admin%` |
|
||||
| `file=` | 可选,要上传的包文件路径;省略时从 info 文件的 `file` 字段读取(通常由 `setup` 写入) |
|
||||
| `info=` | 可选,上传配置信息文件路径,默认 `upload_info.txt`(见下方格式) |
|
||||
| `url=` | 可选,PMC 服务基址,默认 `http://127.0.0.1:8000` |
|
||||
|
||||
**info 配置文件格式**(UTF-8 或 ANSI,每行 `key=value`,`#` 开头为注释):
|
||||
|
||||
```ini
|
||||
url=http://127.0.0.1:8000
|
||||
|
||||
package_name=daily-report
|
||||
package_type_id=1
|
||||
file=E:\dist\MyApp_Setup_5.26.0624.3.exe
|
||||
product_version=5.0.1920.11
|
||||
file_version=5.26.0624.3
|
||||
file_change_log=修复导出 bug
|
||||
description=日报工具
|
||||
wiki_url=https://example.com/wiki
|
||||
product_change_log=首次发布
|
||||
```
|
||||
|
||||
| info 字段 | 必填 | 说明 |
|
||||
|---|---|---|
|
||||
| `package_name` | 是 | 包名称 |
|
||||
| `package_type_id` | 是 | 包类型 ID(整数) |
|
||||
| `file` | 否* | 安装包路径;`upload` 省略 `file=` 时必填,通常由 `setup` 自动写入 |
|
||||
| `product_version` | 是 | 产品版本号 |
|
||||
| `file_version` | 是 | 文件版本号 |
|
||||
| `file_change_log` | 是 | 文件版本更新说明 |
|
||||
| `description` | 否 | 包描述 |
|
||||
| `wiki_url` | 否 | Wiki 链接 |
|
||||
| `product_change_log` | 否 | 产品版本更新说明 |
|
||||
| `url` | 否 | PMC 基址,默认 `http://127.0.0.1:8000` |
|
||||
|
||||
上传流程:先 `POST /auth/login` 获取 Session,再 `POST /admin/packages/upload`(multipart/form-data),最后通过 `/api/packages/latest` 验证版本是否写入。
|
||||
|
||||
HTTP 实现基于 [cpp-httplib](https://github.com/yhirose/cpp-httplib)([`GitVer/httplib.h`](GitVer/httplib.h)),默认支持 `http://`;若需 `https://` 须另行启用 OpenSSL 支持。
|
||||
|
||||
**示例:**
|
||||
|
||||
```powershell
|
||||
# 先设置与 username 同名的环境变量作为登录密码
|
||||
$env:admin = "your_admin_password"
|
||||
```
|
||||
|
||||
```
|
||||
gitver upload username=admin
|
||||
gitver upload username=admin info=E:\upload_info.txt
|
||||
gitver upload username=admin file=E:\dist\app.zip info=E:\upload_info.txt
|
||||
gitver upload username=admin url=http://192.168.1.10:8000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nuitka 与 PyInstaller 常用参数速查
|
||||
@@ -505,6 +594,13 @@ VSVersionInfo(
|
||||
| 44 | `pyinstaller=` 获取当天分支提交次数失败 |
|
||||
| 45 | `pyinstaller=` 版本信息文件无法生成或更新 |
|
||||
| 46 | `pyinstaller=` spec 文件无法注入或修改版本信息 |
|
||||
| 47 | `upload` 缺少必填参数(username),或 file 未指定且 info 中无 file 字段 |
|
||||
| 48 | `upload` 上传文件不存在 |
|
||||
| 49 | `upload` info 文件无效、缺少必填字段,或未设置与 username 同名的环境变量 |
|
||||
| 50 | `upload` PMC 登录失败 |
|
||||
| 51 | `upload` HTTP 上传请求失败 |
|
||||
| 52 | `upload` 上传后未找到对应版本(校验失败或版本冲突) |
|
||||
| 53 | `upload` 网络/HTTP 错误 |
|
||||
|
||||
---
|
||||
|
||||
@@ -592,6 +688,16 @@ gitver setup=0 pid=5
|
||||
# 预期: 使用 pid=5 生成版本号,修改 setup.iss 并调用 ISCC.exe 编译
|
||||
```
|
||||
|
||||
### 验证 upload
|
||||
|
||||
```powershell
|
||||
# 前提:PMC 运行于 http://127.0.0.1:8000,且已有 admin 账号
|
||||
$env:admin = "your_admin_password"
|
||||
# 准备 upload_info.txt(含 package_name、file 等字段,不含 password)
|
||||
gitver upload username=admin
|
||||
# 预期: 读取当前目录 upload_info.txt,返回码 0,PMC 管理页可见新版本
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 回归清单
|
||||
@@ -658,6 +764,17 @@ gitver setup=0 pid=5
|
||||
| `gitver setup=9` | 36 |
|
||||
| `gitver setup=0 pid=abc` | 4 |
|
||||
|
||||
### upload 组
|
||||
|
||||
| 命令 | 预期返回码 |
|
||||
|---|---|
|
||||
| `gitver upload username=admin` | 0 / 47 / 48 / 49 / 50 / 52 / 53 |
|
||||
| `gitver upload username=admin info=E:\upload_info.txt` | 0 / 50 / 52 / 53 |
|
||||
| `gitver upload username=admin file=E:\dist\app.zip info=E:\upload_info.txt` | 0 / 50 / 52 / 53 |
|
||||
| `gitver upload username=admin file=E:\NotExists info=E:\upload_info.txt` | 48 |
|
||||
| `gitver upload username=admin info=E:\NotExists` | 49 |
|
||||
| `gitver upload` | 47 |
|
||||
|
||||
> **注意**:如果打包时提示“未找到安装编译器(ISCC.exe / makensis.exe)”,请手动下载安装对应工具,并将其可执行文件路径(如 `ISCC.exe` 或 `makensis.exe` 所在目录)添加到系统环境变量 `PATH`。常见下载地址:
|
||||
>
|
||||
> - Inno Setup: https://jrsoftware.org/isinfo.php
|
||||
|
||||
Reference in New Issue
Block a user