背景
我的一个go程序原来是在windows环境运行的,为了打包后可以查看exe中的软件信息,引入了goversioninfo
现在打算在linux arm64上运行,众所周知golang支持交叉编译的,于是我在我的打包脚本中加入了支持linux编译相关代码,可是打包发现遇到问题了...
遇到问题
打包时遇到报错: unknown ARM relocation type 7
参考了 https://github.com/golang/go/issues/47758
可是后来发现他是要编译 windows arm64 我加了也没用啊(还不知道错的原因)
这个时候我都怀疑是不是我的windows是amd64的 无法编译arm64呢, 那我就把代码搬到linux arm64环境试试咯
在kylinV10 arm64机器上安装了go1.22,拉取了我的程序代码,开始编译,还是报上面那个错误啊,这时我发现我的怀疑应该是错的,回头又看了看go/issues/47758,主要问题是goversioninfo的问题哦,只要删除main.go开头的//go:generate goversioninfo就不会报错了
解决问题
windows环境下手动执行goversioninfo,然后再build;
goversioninfo && go build -ldflags="-s -w --extldflags '-static -fpic' -X 'main.productName=程序名称' -X 'main.buildVersion=版本号' -X 'main.buildTime=构建时间'"
linux环境不需要执行goversioninfo,直接build;
set CGO_ENABLED=0 && set GOOS=linux && set GOARCH=arm64 && go build -ldflags="-s -w --extldflags '-static -fpic'"
后记
今天居然同一个应用打包又遇到这个错误了,费了我一整天,最后发现问题了:
1. 试用windows打包后,根目录由goversioninfo生成了resource.syso文件
2. 再试用linux打包就报错了,所以resource.syso要先删掉
顺便附上我的打包脚本pack.py,放项目根目录,打包的时候执行python pack.py就可以了
#!/usr/bin/python
# -*- coding: utf-8 -*-
__author__ = "hbh112233abc@163.com"
import json
import os
import re
import subprocess
import time
from typing import Any, List, Union,Tuple
pwd = os.path.abspath(os.path.dirname(__file__))
app_name = os.path.basename(pwd)
def ask(title: str, default: Any = "", range: Union[List,Tuple,re.Pattern,str] = None) -> str:
"""输入交互
Args:
title (str): 对应内容
default (str, optional): 默认值. Defaults to ''.
range (mixed, optional): 限制内容. Defaults to None.
: list|tuple 限制输入内容范围
: re.Pattern 正则匹配
: str(require) 必须有值
Returns:
str: 用户输入内容
"""
while True:
options = ""
if isinstance(range, (list, tuple)):
options = "\noptions:\n "+ "\n ".join([str(x) for x in range])+"\n"
result = input(f"please input {title} {options}[default:{default}] => ").strip()
if default:
result = result or default
if result:
if range is None:
return result
if isinstance(range, (list, tuple)):
if not result in range:
print(f"{title}需要在{range}范围内")
continue
if isinstance(range, re.Pattern):
if not range.match(result):
print(f"{title}格式错误")
continue
if range == "require" and not result:
continue
return result
def init_version_info() -> dict:
content = '{ "FixedFileInfo": { "FileVersion": { "Major": 1, "Minor": 0, "Patch": 0, "Build": 0 }, "ProductVersion": { "Major": 1, "Minor": 0, "Patch": 0, "Build": 0 }, "FileFlagsMask": "3f", "FileFlags ": "00", "FileOS": "040004", "FileType": "01", "FileSubType": "00" }, "StringFileInfo": { "Comments": "程序描述", "CompanyName": "公司名称", "FileDescription": "文件说明", "FileVersion": "v1.0.0.0", "InternalName": "程序内部名称", "LegalCopyright": "Copyright (c) 2021 efileyun.com", "LegalTrademarks": "", "OriginalFilename": "原始文件名", "PrivateBuild": "", "ProductName": "产品名称", "ProductVersion": "v1.0.0.0", "SpecialBuild": "" }, "VarFileInfo": { "Translation": { "LangID": "0804", "CharsetID": "04B0" } }, "IconPath": "icon.ico", "ManifestPath": "" }'
info = json.loads(content)
defaultName = os.path.basename(pwd)
info["StringFileInfo"]["ProductName"] = info["StringFileInfo"][
"InternalName"
] = ask("产品名称")
info["StringFileInfo"]["Comments"] = info["StringFileInfo"][
"FileDescription"
] = ask("产品描述")
info["StringFileInfo"]["OriginalFilename"] = ask("原始文件名", f"{defaultName}.exe")
info["StringFileInfo"]["CompanyName"] = ask("公司名称")
info["StringFileInfo"]["LegalCopyright"] = ask("版权说明")
info["IconPath"] = ask("logo", "resource/icon.ico")
info["ManifestPath"] = ask("manifest", f"resource/{app_name}.exe.manifest")
return info
def read_version_info():
"""读取版本信息
Returns:
dict: 版本信息json对象
"""
file = "versioninfo.json"
if not os.path.isfile(file):
return init_version_info()
else:
with open(file, "r", encoding="utf-8") as f:
info = json.load(f)
return info
def ask_version_info(info: dict):
"""问询版本号
Args:
info (dict): 版本信息
Returns:
dict: 更新后的版本信息
"""
defaultVersion = f"{info['FixedFileInfo']['FileVersion']['Major']}.{info['FixedFileInfo']['FileVersion']['Minor']}.{info['FixedFileInfo']['FileVersion']['Patch']}"
version = ask("版本号", defaultVersion, re.compile(r"^(\d+)\.(\d+)\.(\d+)$"))
major, minor, patch = version.split(".")
build = get_build_version()
major = int(major)
minor = int(minor)
patch = int(patch)
build = int(build)
info["FixedFileInfo"]["FileVersion"]["Major"] = major
info["FixedFileInfo"]["FileVersion"]["Minor"] = minor
info["FixedFileInfo"]["FileVersion"]["Patch"] = patch
info["FixedFileInfo"]["FileVersion"]["Build"] = build
info["FixedFileInfo"]["ProductVersion"]["Major"] = major
info["FixedFileInfo"]["ProductVersion"]["Minor"] = minor
info["FixedFileInfo"]["ProductVersion"]["Patch"] = patch
info["FixedFileInfo"]["ProductVersion"]["Build"] = build
info["StringFileInfo"]["FileVersion"] = info["StringFileInfo"][
"ProductVersion"
] = f"{major}.{minor}.{patch}.{build}"
return info
def write_version_info(info: dict):
with open("versioninfo.json", "w", encoding="utf-8") as f:
json.dump(info, f, indent=2, ensure_ascii=False)
def get_build_version() -> str:
if os.path.exists(".svn"):
return get_svn_version()
if os.path.exists(".git"):
return get_git_version()
return "0"
def get_svn_version() -> str:
# svn 版本号
res = subprocess.Popen("SubWCRev ./", stdout=subprocess.PIPE).communicate()
print(res)
output = res[0].decode("utf-8")
result = re.findall(r"Last committed at revision (\d+)", output)
print(result)
svn_version = result[0]
return int(svn_version)
def get_git_version() -> int:
res = subprocess.Popen(f"git rev-parse HEAD", stdout=subprocess.PIPE).communicate()
print(res)
output = res[0].decode("utf-8")
return int(output[:10], 16)
def build(info:dict):
res = os.system("go mod vendor")
now = time.strftime("%Y-%m-%d %H:%M:%S")
app = f"{app_name}-{info['goos']}-{info['arch']}"
build_cmd = ""
if info["goos"] == "windows":
app += ".exe"
if info["goos"] == "linux":
build_cmd = f'''set CGO_ENABLED={os.environ["CGO_ENABLED"]} && set GOOS={info["goos"]} && set GOARCH={info["arch"]} && '''
build_cmd = f'''go build -o {app} -ldflags="-s -w --extldflags '-static -fpic' -X 'main.productName={info['StringFileInfo']['ProductName']}' -X 'main.buildVersion={info['StringFileInfo']['ProductVersion']}' -X 'main.buildTime={now}'" '''
res = os.system(build_cmd)
# -s -w 去掉调试信息,可以减小构建后文件体积
# --extldflags "-static -fpic" 完全静态编译[2],这样编译生成的文件就可以任意放到指定平台下运行,而不需要运行环境配置
print(build_cmd, res)
return app
def main():
global app_name
app_name = ask("app_name", app_name)
goos = ask("goos", "windows", ["windows", "linux", "darwin","freebsd"])
arch = ask("goarch", "amd64", ["amd64", "386", "arm64",])
os.environ["GOOS"] = goos.strip()
os.environ["GOARCH"] = arch.strip()
os.environ["CGO_ENABLED"] = "0"
if "arm" in arch:
os.environ["GOARM"]="7"
info = read_version_info()
info = ask_version_info(info)
write_version_info(info)
info['goos'] = goos
info['arch'] = arch
os.system("go env")
if os.path.isfile("resource.syso"):
os.remove("resource.syso")
if goos == "windows":
print('windows env execute: goversioninfo')
os.system("goversioninfo")
app = build(info)
if not os.path.isfile(app):
print(f"{app} not found")
return
zip_cmd = f"upx.exe --best {app}"
os.system(zip_cmd)
os.system(f"explorer.exe {pwd}")
if __name__ == "__main__":
main()