原因
学会了用Go编译动态库给Python和C调用,就想着编译一个静态库试试。在网上翻了很久,找到的帖子都不能用,至少2023年以前及Go1.20以前的都不能用。在 Go 1.20 之前,标准库被安装到、$GOROOT/pkg/$GOOS_$GOARCH,从 Go 1.20 开始,默认情况下,标准库被构建和缓存,但未安装。因此,$GOROOT/pkg/下根本没有$GOOS_$GOARCH文件夹,而是被缓存到go-build文件夹,我的是C:\Users\unknow\AppData\Local\go-build。为了保持干净,我也不打算把标准库安装到$GOROOT/pkg/$GOOS_$GOARCH。
目录、文件说明
e:\gogo\src\staticlib\go.mod
e:\gogo\src\staticlib\main.go
e:\gogo\src\staticlib\abc\foo.go
想法是:把abc\foo.go编译为静态库foo.a,然后由main.go去掉用foo.a实现功能。
main.go内容:
package main
import "foo"
func main() {
foo.Bar()
}
foo.go内容:
package foo
import "fmt"
func Bar() {
fmt.Println("foobar.")
}
编译foo.a出错
刚开始按照网上找的命令第一步就出错, 提示找不到fmt包
cd foo
go tool compile -pack -p foo foo.go
foo.go:3:8: could not import fmt (file not found)
但是,go build foo.go
不会出错。
解决编译
1.追踪go build 过程
go build -x -v -a foo.go 2>&1 | more > br.txt
将go build 的全部步骤保存到br.txt这个文件, 发现在编译foo.go之前编译了并转存了标准库到缓存文件夹
2. 找到foo.go需要用到的fmt
通过命令cp "$WORK\\b002\\_pkg_.a" "C:\\Users\\unknow\\AppData\\Local\\go-build\\af\\af957af9edcde92623760da8bbf015a49bf195d22b117b010e80c51678fda67d-d"
找到缓存后的fmt.a及其名称。
将"C:\\Users\\unknow\\AppData\\Local\\go-build\\af\\af957af9edcde92623760da8bbf015a49bf195d22b117b010e80c51678fda67d-d"
复制到abc目录,并改名为fmt.a
.
3. 创建importcfg文件供编译器使用
importcfg.txt
packagefile fmt=fmt.a
- 正确编译foo.a
go tool compile -p foo -o foo.a -importcfg importcfg.txt foo.go
此命令执行成功后会在当前文件夹下(foo)生成foo.a库文件。此处如果不使用 -p foo
指定导入路径,在编译main.go的时候会找不到包名错误:
go tool compile -o ma.o -I abc main.go
<unknown line number>: internal compiler error: have package "<unlinkable>" (0xc0000609b0), want package "foo" (0xc0003d9a40)
- 编译main.go
回到staticlib目录下,执行
go tool compile -o ma.o -I abc main.go
如果成功,就能生成ma.o目标文件。
链接
链接使用的命令是:
go tool link -o ma.exe -L abc ma.o
但是会提示找不到很多标准库,如errors.o, 在这个示例中,这些库有42个
D:\Go\pkg\tool\windows_amd64\link.exe: cannot open file D:\Go\pkg\windows_amd64\errors.o: open D:\Go\pkg\windows_amd64\errors.o: The system cannot find the path specified.
解决思路跟找fmt.a是一样的,也是从go build -x -v -a foo.go
的结果中找到这些库,只是工作量比较大。
为此,我用python写了一个粗糙的脚本完成这些工作。只需要执行一次,就会把需要的库文件复制到staticlib\abc\module文件夹下,以后就直接指定路径即可。
import os
import shutil
import re
import time
TotalFiles=0
FOOLIB="go tool compile -pack -importcfg importcfg.txt -p foo foo.go"
GOBUILD="go build -x -v -a foo.go 2>&1 | more > br.txt"
if not os.path.exists("br.txt"):
os.system(GOBUILD)
PKGFILE="findstr packagefile br.txt > pkgfile.txt"
if not os.path.exists("pkgfile.txt"):
os.system(PKGFILE)
CPFILE=r"findstr ^cp.*internal$ br.txt >cpfile.txt"
if not os.path.exists("cpfile.txt"):
os.system(CPFILE)
if os.path.exists("module"):
shutil.rmtree("module")
os.mkdir("module")
FD={}
f=open("pkgfile.txt", "r")
tclist=f.read().splitlines()
f.close()
for x in tclist:
v=x.split("=")[0].split(" ")[1].replace("/","\\")
k=x.split("=")[1].split("\\")[-2]
if not k in FD:
FD[k]=[v]
f=open("cpfile.txt", "r")
fclist=f.read().splitlines()
f.close()
reg=re.compile(r'^cp.*# internal$')
for x in fclist:
if reg.match(x):
fk=x.split(" ")[1].split("\\\\")[1]
fv=x.split(" ")[2].replace("\\\\","\\").strip('"')
if fk in FD:
FD[fk].append(fv)
for k,v in FD.items():
dst = os.path.join("module","\\".join(v[0].split("\\")[:-1]))
fn = os.path.join(dst, v[0].split("\\")[-1]+".a")
fv = v[1]
print(fv,"=>", fn)
if not os.path.exists(dst):
os.makedirs(dst)
shutil.copyfile(fv, fn)
TotalFiles += 1
time.sleep(1)
print(f"Total Files {TotalFiles} Copied to 'module'")
print("Create importcfg.txt:")
with open("importcfg.txt", "w") as f:
f.write("packagefile fmt=module\\fmt.a\n")
result = os.popen(FOOLIB)
if result.read() == "" :
print("build successful")
print("now can build main.go with foo.a")
print("go tool compile -o main.o -I abc main.go")
print("go tool link -o m.exe -L abc -L abc\module main.o")
os.chdir("..")
MAINOBJ="go tool compile -o main.o -I abc main.go"
r = os.popen(MAINOBJ)
if r.read()=="":
MAINEXE="go tool link -o m.exe -L abc -L abc\module main.o"
r = os.popen(MAINEXE)
if r.read()=="":
r = os.popen("m.exe")
print(r.read())
else:
print(r.read())
else:
print(result.read())
执行后,目录结构变为
e:\gogo\src\staticlib\go.mod
e:\gogo\src\staticlib\main.go
e:\gogo\src\staticlib\main.o
e:\gogo\src\staticlib\m.exe
e:\gogo\src\staticlib\abc\foo.go
e:\gogo\src\staticlib\abc\foo.a
e:\gogo\src\staticlib\abc\module\*.a
验证foo.a
在e:\gogo\src\staticlib下新建目录def,将foo.a 复制到此目录下。新建main.go,内容为:
import "foo"
func main(){
foo.Bar()
}
编译:
go tool compile -o main.o -I . main.go
链接:
go tool link -o m.exe -L . -L ..\abc\module main.o
成功生成m.exe并实现功能。
E:\gogo\src\staticlib\def\foo.a
E:\gogo\src\staticlib\def\m.exe
E:\gogo\src\staticlib\def\main.go
E:\gogo\src\staticlib\def\main.o