Summary
Replace the CGO-based SQLite driver (github.com/mattn/go-sqlite3) with the pure-Go driver (modernc.org/sqlite). This eliminates the CGO build requirement, which is the sole blocker for cross-compiling to macOS and Linux ARM targets (see #TBD).
Background
The project currently uses github.com/mattn/go-sqlite3, which requires CGO_ENABLED=1 and a C compiler at build time. This has several consequences:
- Cross-compilation is impractical — building for macOS or Linux ARM from a Linux amd64 CI runner requires platform-specific C cross-compilers (osxcross, aarch64-linux-gnu-gcc), adding significant CI complexity
- Docker images need
sqlite-libs at runtime (currently installed in both Dockerfile and Dockerfile.ci)
- E2E test helper (
testutil/binary.go) must explicitly set CGO_ENABLED=1 when building the test binary
modernc.org/sqlite is a mechanically translated pure-Go port of SQLite. It uses the same database/sql interface with the driver name "sqlite" instead of "sqlite3". DSN pragma syntax differs slightly.
Scope of change
The CGO surface is well-contained in this codebase:
| File |
What changes |
internal/audit/audit.go |
Import: mattn/go-sqlite3 → modernc.org/sqlite. Driver name: "sqlite3" → "sqlite". DSN pragma syntax: ?_journal_mode=WAL&_foreign_keys=on → ?_pragma=journal_mode(WAL)&_pragma=foreign_keys(on) |
internal/audit/audit.go |
In-memory DSN: file::memory:?mode=memory&cache=shared&_foreign_keys=on → file::memory:?mode=memory&cache=shared&_pragma=foreign_keys(on) |
internal/audit/migrate_test.go |
Same driver name and DSN adjustments as above |
go.mod |
Remove github.com/mattn/go-sqlite3, add modernc.org/sqlite |
.goreleaser.yaml |
Remove CGO_ENABLED=1 from env (or set to 0) |
Dockerfile |
Remove CGO_ENABLED=1 from build command |
Dockerfile |
Remove sqlite-libs from runtime apk add |
Dockerfile.ci |
Remove sqlite-libs from runtime dependencies |
internal/testutil/binary.go |
Remove CGO_ENABLED=1 from test binary build env |
Implementation plan
1. Swap the driver (TDD)
Tests first:
- Update
internal/audit/audit_test.go and migrate_test.go driver name and DSN references
- Run tests — they will fail because the new driver isn't imported yet
Implementation:
go get modernc.org/sqlite
- In
internal/audit/audit.go:
- Replace
_ "github.com/mattn/go-sqlite3" with _ "modernc.org/sqlite"
- Change
sql.Open("sqlite3", dsn) → sql.Open("sqlite", dsn)
- Update DSN pragma syntax for both file-based and in-memory paths
go mod tidy to remove mattn/go-sqlite3
Verify: go test ./internal/audit/... passes
2. Remove CGO build requirements
.goreleaser.yaml: change CGO_ENABLED=1 to CGO_ENABLED=0
Dockerfile: remove CGO_ENABLED=1 from go build, remove sqlite-libs from apk add
Dockerfile.ci: remove sqlite-libs install
internal/testutil/binary.go: remove CGO_ENABLED=1 from build env, update comment
Verify: go test ./... passes, go build ./cmd/virtwork succeeds without CGO
3. Validate existing audit database compatibility
modernc.org/sqlite reads/writes the same SQLite file format
- Existing
virtwork.db files will continue to work without migration
- WAL journal mode and foreign keys behave identically
Acceptance criteria
References
Summary
Replace the CGO-based SQLite driver (
github.com/mattn/go-sqlite3) with the pure-Go driver (modernc.org/sqlite). This eliminates the CGO build requirement, which is the sole blocker for cross-compiling to macOS and Linux ARM targets (see #TBD).Background
The project currently uses
github.com/mattn/go-sqlite3, which requiresCGO_ENABLED=1and a C compiler at build time. This has several consequences:sqlite-libsat runtime (currently installed in bothDockerfileandDockerfile.ci)testutil/binary.go) must explicitly setCGO_ENABLED=1when building the test binarymodernc.org/sqliteis a mechanically translated pure-Go port of SQLite. It uses the samedatabase/sqlinterface with the driver name"sqlite"instead of"sqlite3". DSN pragma syntax differs slightly.Scope of change
The CGO surface is well-contained in this codebase:
internal/audit/audit.gomattn/go-sqlite3→modernc.org/sqlite. Driver name:"sqlite3"→"sqlite". DSN pragma syntax:?_journal_mode=WAL&_foreign_keys=on→?_pragma=journal_mode(WAL)&_pragma=foreign_keys(on)internal/audit/audit.gofile::memory:?mode=memory&cache=shared&_foreign_keys=on→file::memory:?mode=memory&cache=shared&_pragma=foreign_keys(on)internal/audit/migrate_test.gogo.modgithub.com/mattn/go-sqlite3, addmodernc.org/sqlite.goreleaser.yamlCGO_ENABLED=1from env (or set to 0)DockerfileCGO_ENABLED=1from build commandDockerfilesqlite-libsfrom runtimeapk addDockerfile.cisqlite-libsfrom runtime dependenciesinternal/testutil/binary.goCGO_ENABLED=1from test binary build envImplementation plan
1. Swap the driver (TDD)
Tests first:
internal/audit/audit_test.goandmigrate_test.godriver name and DSN referencesImplementation:
go get modernc.org/sqliteinternal/audit/audit.go:_ "github.com/mattn/go-sqlite3"with_ "modernc.org/sqlite"sql.Open("sqlite3", dsn)→sql.Open("sqlite", dsn)go mod tidyto removemattn/go-sqlite3Verify:
go test ./internal/audit/...passes2. Remove CGO build requirements
.goreleaser.yaml: changeCGO_ENABLED=1toCGO_ENABLED=0Dockerfile: removeCGO_ENABLED=1fromgo build, removesqlite-libsfromapk addDockerfile.ci: removesqlite-libsinstallinternal/testutil/binary.go: removeCGO_ENABLED=1from build env, update commentVerify:
go test ./...passes,go build ./cmd/virtworksucceeds without CGO3. Validate existing audit database compatibility
modernc.org/sqlitereads/writes the same SQLite file formatvirtwork.dbfiles will continue to work without migrationAcceptance criteria
go test ./...passes withCGO_ENABLED=0go build ./cmd/virtworksucceeds withCGO_ENABLED=0virtwork.dbfiles are readable by the new drivermattn/go-sqlite3references remain in the codebaseCGO_ENABLED=1references remain in build filesReferences
modernc.org/sqlite: https://pkg.go.dev/modernc.org/sqlitemattn/go-sqlite3: https://github.com/mattn/go-sqlite3