记录VSCode中写Go代码切换Sqlite无CGO依赖版本的过程以及遇到的五个问题
# 前言
我的 xirang (opens new window) 系统之前引入 sqlite 功能的时候,使用的驱动包是 github.com/mattn/go-sqlite3 v1.14.15
,这个库现在在 go 项目当中,引用最多,应用最广,看项目 Used By 达到了 71k,俨然成为这一方面的标准,但有一个最大的问题就是,这是一个 C 语言实现的库,如果要应用这个库,那么你的环境就需要解决这个 CGO 依赖。
如果是在 CentOS 中运行,会看到如下报错:
[error] failed to initialize database, got error Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work. This is a stub
2023-02-24 12:30:41 PANIC common/database.go:32 github.com/eryajf/go-ldap-admin/public/common.initSqlite3 failed to connect sqlite3: Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work. This is a stub
panic: failed to connect sqlite3: Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work. This is a stub
2
3
我的 Mac 没有遇到这个错误,但其他人 Windows 上也有遇到这个错误。
原本引入 sqlite 为了方便的,结果因为这个问题,反而变得麻烦起来,我看了一下 centos 上想要解决这个问题,要耗费相当一番功夫。
于是决定寻找一个相对优雅的解决方案。
# 方案
通过网上检索,我找到了一篇这样的文章:golang 不使用 cgo 的 sqlite 驱动推荐 (opens new window),这篇文章的作者遇到的困境与我是一致的,他发现了一个无 CGO 版本的库 modernc.org/sqlite
,因为这个库是在 gitlab,不太方便,于是作者给搬到了 github,但作者维护的并不及时,也不够规范(没有 go.mod 文件),我便没能应用起来。
后来又来到 gorm 的 issue 区寻找线索,发现了这个问题:Driver for pure Go sqlite implimentation (opens new window),其中一名开发者分享了他维护的一个库:glebarez/sqlite (opens new window),能够与 gorm 完美结合起来使用。
# 实践
说干就干,准备选用这个库来作为 sqlite 的 orm 驱动,具体代码文件内容可见:
点击查看
package common
import (
"fmt"
"github.com/eryajf/xirang/config"
"github.com/eryajf/xirang/model"
"github.com/glebarez/sqlite"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 全局数据库对象
var DB *gorm.DB
// 初始化数据库
func InitDB() {
switch config.Conf.Database.Driver {
case "mysql":
DB = ConnMysql()
case "sqlite3":
DB = ConnSqlite()
}
dbAutoMigrate()
}
// 自动迁移表结构
func dbAutoMigrate() {
_ = DB.AutoMigrate(
&model.User{},
&model.Role{},
&model.Group{},
&model.Menu{},
&model.Api{},
&model.OperationLog{},
)
}
func ConnSqlite() *gorm.DB {
db, err := gorm.Open(sqlite.Open(config.Conf.Database.Source), &gorm.Config{
// 禁用外键(指定外键时不会在mysql创建真实的外键约束)
DisableForeignKeyConstraintWhenMigrating: true,
})
if err != nil {
Log.Panicf("failed to connect sqlite3: %v", err)
}
return db
}
func ConnMysql() *gorm.DB {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&collation=%s&%s",
config.Conf.Mysql.Username,
config.Conf.Mysql.Password,
config.Conf.Mysql.Host,
config.Conf.Mysql.Port,
config.Conf.Mysql.Database,
config.Conf.Mysql.Charset,
config.Conf.Mysql.Collation,
config.Conf.Mysql.Query,
)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
// 禁用外键(指定外键时不会在mysql创建真实的外键约束)
DisableForeignKeyConstraintWhenMigrating: true,
})
if err != nil {
Log.Panicf("初始化mysql数据库异常: %v", err)
}
// 开启mysql日志
if config.Conf.Mysql.LogMode {
db.Debug()
}
Log.Infof("初始化mysql数据库完成! dsn: %s", showDsn)
return db
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
在包引用的地方,直接把 "gorm.io/driver/sqlite"
改成 "github.com/glebarez/sqlite"
即可,但这只是完成了很小的一步,接下来遇到的问题才是大头。
# 问题集锦
# go 版本
因为依赖库对 go 版本支持在 1.18 以上,而我之前一直用的 1.17,这次看起来不得不升级一下版本了,于是将自己的 go 版本升级为 1.18.10。这个升级为后续的一个问题埋下伏笔,暂时按下不表,且看版本依赖处理时遇到的第一个问题。
# mod 依赖的处理
当我把编码搞定之后,准备直接运行 go mod tidy -v
,但却发现各种过不去,这个时候我选择了两种方案来解决这个问题,思路重要,值得记录。
- 方案一:把 mod 文件清空,然后重新 tidy,让依赖重新自检,但是这样子项目中其他的依赖也大多拉取了最新的版本,反而引出了更多的报错,这不是一个可取的方案。
- 方案二:如果暂时不知道怎么写,那就先看看别人怎么写的,于是来到
"github.com/glebarez/sqlite"
依赖的 User By 页面,开始寻找能够借鉴学习的项目案例,经过一番查找,找到了: https://github.com/YPSI-SAS/Golang-GORM-tutoriel , 然后自己再把源码拉下来,按照自己的需求进行调配,配置完毕之后,tidy 检查一切没有问题。最后再把两个项目的 mod 文件进行比对,把 orm 相关的版本拷贝到主项目中,之后果然解决了问题。
有时候依赖的自检会受限于当前项目的各种版本,而且也并不能盲目升级,尤其是针对于生产的业务。
# IDE 的一个报错
当我把代码按照规划的方案编码之后,就看到了如下的报错,而且这个报错在好几个地方都出现。
错误信息如下:
实际程序编译以及运行都没有问题,这是因为依赖包大版本升级,但是 Vscode 还应用老的缓存导致,此时将当前窗口关闭,重新打开项目,即可看到这个报错消失。
# golangci-lint 的问题
一切搞定之后,我打算把代码合并到主分支,原以为经过几个小时的折腾,终于可以点下合并就休息了,结果却收到了 action 的 lint 检查失败,详见:action log (opens new window),我于是在本地开始调试 lint。
在本地调试的时候,却出现了如下错误,好吧,我似乎应该把 lint 放在提交代码之前,这是个需要优化的地方。
当我在本地运行 golangci-lint 的时候,看到了如下错误:
$ golangci-lint version
panic: load embedded ruleguard rules: rules/rules.go:13: can't load fmt
goroutine 1 [running]:
github.com/go-critic/go-critic/checkers.init.22()
/Users/eryajf/software/go/pkg/mod/github.com/go-critic/go-critic@v0.6.3/checkers/embedded_rules.go:47 +0x52c
2
3
4
5
6
之后在 golangci-lint 仓库的 issue 区看到了一个说法: https://github.com/golangci/golangci-lint/issues/2374
如果 go 版本升级了,那么这个工具也需要升级,而且还不能一下子直接安装最新版本,因为当下用的 go 版本并不是最新版本的。这个时候来到 golangci-lint 的 release 页面,通过关键字进行搜索,找到支持 1.19 之前的那个版本,就安装这个版本的就可以。
执行如下命令进行安装:
$ go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.47.3
然后再来到本地跑 lint 发现就没有什么报错了。接着把 action (opens new window) 文件的 golang 版本以及插件版本都指定为对应合适的版本,再次运行检查就不会报错了。
# 关于前置 lint
我们使用 pre-commit (opens new window) 来完成这一功能。
安装:
$ pip3 install pre-commit
查看版本:
$ pre-commit --version
pre-commit 3.1.0
2
然后在项目根目录添加如下配置文件.pre-commit-config.yaml
:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.1.0
hooks:
- id: check-yaml
- id: trailing-whitespace
- id: check-added-large-files
- repo: https://github.com/golangci/golangci-lint # golangci-lint hook repo
rev: v1.47.3 # golangci-lint hook repo revision
hooks:
- id: golangci-lint
name: golangci-lint
description: Fast linters runner for Go.
entry: golangci-lint run --fix
types: [go]
language: golang
pass_filenames: false
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
然后运行如下命令将 hooks 载入到 git 配置文件中:
$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
2
然后将代码某处的 err 错误忽略不做处理,此时提交代码看看是否会检查:
$ gcmsg 'test pre check'
Check Yaml...............................................................Passed
Trim Trailing Whitespace.................................................Passed
Check for added large files..............................................Passed
golangci-lint............................................................Failed
- hook id: golangci-lint
- exit code: 1
config/config.go:30:11: ineffectual assignment to err (ineffassign)
workDir, err := os.Getwd()
^
2
3
4
5
6
7
8
9
10
11
如此就实现了一个简单的提交前的 lint 检查,这里只是先简单抛砖引玉,后续再深入研究一下pre-commit-hooks
,是个很好很重要的东西。