记一次golang交叉编译的问题

文章讲述了作者在将Go程序从Windows环境移植到LinuxARM64时遇到的问题,涉及goversioninfo的使用和不同操作系统下的编译设置。解决了一个关于unknownARMrelocationtype7的错误,并分享了Windows和Linux环境下的打包脚本和注意事项。
摘要由CSDN通过智能技术生成

背景

我的一个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()

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值