Skip to content

Replace compiled migrations binary with in-process yaegi interpreter#1

Merged
ocomsoft merged 3 commits into
mainfrom
claude/use-yaegi-instead-compile-09rru
May 5, 2026
Merged

Replace compiled migrations binary with in-process yaegi interpreter#1
ocomsoft merged 3 commits into
mainfrom
claude/use-yaegi-instead-compile-09rru

Conversation

@ocomsoft
Copy link
Copy Markdown
Owner

@ocomsoft ocomsoft commented May 5, 2026

Summary

This PR eliminates the need to compile the migrations module into a temporary binary. Instead, makemigrations migrate now loads and interprets migration .go files in-process using the yaegi Go interpreter, removing the Go toolchain requirement at runtime.

Key Changes

  • Removed binary compilation pipeline: Deleted buildMigrationsBinary() and all supporting functions (upgradeMakemigrationsVersion(), findLocalMakemigrations(), findParentGoVersion(), isFullGoVersion(), stderrSupportsColor(), warnf()) that managed temporary builds, workspace setup, and version upgrades.

  • New in-process interpreter: Added internal/interp/loader.go with LoadRegistry() that:

    • Scans the migrations directory for .go files (excluding main.go and *_test.go)
    • Rewrites package declarations from main to migrations (yaegi cannot import main packages)
    • Builds an in-memory filesystem and evaluates all files with yaegi
    • Intercepts migrate.Register() calls to populate a fresh *migrate.Registry per load
    • Enables multiple loads in a single process without duplicate-registration panics
  • Simplified queryDAG() and ExecuteMigrate(): Both now call interp.LoadRegistry() directly, build the migration graph, and invoke operations in-process instead of spawning external processes.

  • Symbol map infrastructure: Added migrate/symbols/ package with:

    • symbols.go: Public Register() API for extending the yaegi symbol map with third-party packages
    • migrate.go: Auto-generated symbol map for the migrate package (via yaegi extract)
    • Allows users to hand-edit migrations with custom imports by wrapping the CLI and registering extra symbols
  • Updated documentation:

    • New docs/extending-yaegi-symbols.md guide for adding third-party packages to interpreted migrations
    • Updated docs/commands/migrate.md to reflect in-process execution
    • Updated README to emphasize no build step and yaegi-based interpretation
  • Driver registration: Added cmd/drivers.go to blank-import SQL drivers (mysql, sqlserver, sqlite3) since migrations now run in the CLI's process rather than a standalone binary.

  • Removed CLI flags: --verbose and --dont-upgrade flags are no longer needed (no build step, no version management).

  • Updated dependencies: Added github.com/traefik/yaegi v0.16.1 and SQL drivers to go.mod; bumped Go version to 1.25.7.

Implementation Details

  • Migration files are rewritten at parse time to use a virtual package name (migrations) while preserving all comments and formatting via go/ast and go/printer.
  • The perLoadSymbols() function clones the global symbol map and overrides migrate.Register to write into a per-load registry, enabling isolation.
  • Tests in internal/interp/loader_test.go verify correct loading, empty directory handling, and isolation between consecutive loads.
  • The generated migrations/main.go remains optional for backward compatibility (users can still go build standalone if desired).

https://claude.ai/code/session_014Wn4chfaL2aTyjxMXeNBmm

claude added 3 commits May 5, 2026 04:19
Replace the build-and-fork approach with in-process yaegi interpretation:

- cmd/migrate now loads migrations/*.go via yaegi and runs the embedded
  migrate.App directly — no `go build`, no temp binary, no GOWORK
  synthesis, no toolchain version dance.
- cmd/go_migrations queryDAG calls BuildGraph + ToDAGOutput in-process
  on a freshly-loaded *migrate.Registry instead of round-tripping
  `dag --format json` through a child binary.
- internal/interp/loader.go owns the loader: it rewrites each
  migration file's `package main` to a virtual package, places the
  files in an in-memory FS, and overrides migrate.Register with a
  per-call shim so multiple loads can coexist in one process.
- migrate/symbols/ holds the yaegi symbol map (`yaegi extract`-generated
  for the migrate package) plus a Register() hook so users with
  third-party imports in their migrations can extend it.
- cmd/drivers.go blank-imports github.com/mattn/go-sqlite3 in the CLI
  itself; previously users registered drivers via their own
  migrations/main.go, but with yaegi the host process is what runs SQL.

The migrations/ directory remains a buildable Go module so IDE tooling
keeps working and a standalone binary is still an option for users who
want one. Removed the now-dead helpers buildMigrationsBinary,
findLocalMakemigrations, upgradeMakemigrationsVersion, isFullGoVersion
(~250 lines of build-toolchain plumbing).

https://claude.ai/code/session_014Wn4chfaL2aTyjxMXeNBmm
- cmd/drivers.go: blank-import github.com/go-sql-driver/mysql and
  github.com/microsoft/go-mssqldb so `makemigrations migrate` works
  out of the box for all four supported database types (postgres was
  already covered transitively via internal/providers/postgresql).
- docs/extending-yaegi-symbols.md: explain the wrapper-CLI pattern
  for users whose hand-edited migrations import third-party packages
  not in the default symbol map. Covers both the recommended path
  (mmsymbols.Register from a generated `yaegi extract` file in a
  thin main wrapper) and the standalone-binary fallback.
- README: link the new guide.

https://claude.ai/code/session_014Wn4chfaL2aTyjxMXeNBmm
makemigrations migrate runs migration files in-process via yaegi and never
invokes main(). The generated main.go remains as an optional escape hatch
for users who want to `go build` the migrations directory into a standalone
binary. The new file-level comment makes that explicit so users know the
file is safe to delete (without losing IDE type-checking, which goes
through go.mod).

https://claude.ai/code/session_014Wn4chfaL2aTyjxMXeNBmm
@ocomsoft ocomsoft merged commit 119a028 into main May 5, 2026
1 of 3 checks passed
@github-advanced-security
Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Comment thread internal/interp/loader.go
// package, dedupes imports, and runs all init() funcs in source order.
fsys := fstest.MapFS{}
for _, path := range migFiles {
data, readErr := os.ReadFile(path)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants