cmd/extract
May 24, 2026 ยท View on GitHub
Code generator that reflects on Go package source to emit
reflect.Valuebindings: standaloneImportPackageValuesmaps for embedding external packages, or stdlib binding files for mvm's build.
Overview
cmd/extract walks a Go package's source tree and identifies its
exported symbols (const, var, type, func). What it emits depends
on the mode:
- Default mode writes a standalone
var <pkg>Valuesmap ofreflect.Valueentries that an embedder passes to(*interp.Interp).ImportPackageValues, exposing a native package to interpreted code. The target is given as an import path; its source is located withgo list. -stdlibmode instead registers the symbols intostdlib.Valuesvia aninit()and is the only producer of files understdlib/core/andstdlib/ext/.-listmode prints a raw<kind> <name>listing, for debugging what a package exposes.
The -stdlib mode is invoked indirectly through make generate, which
calls go generate ./stdlib (driven by directives in stdlib/gen.go)
and then loops over go tool dist list to emit one syscall binding
per platform.
Usage
Three modes, the default selected when neither -stdlib nor -list is
set:
# Default mode: write an ImportPackageValues binding map for an import
# path (source located via `go list` unless [dir] is given).
go run ./cmd/extract [-o out.go] [-pkg name] <import-path> [dir]
# Stdlib mode: write a binding file under ./core/ or ./ext/.
go run ./cmd/extract -stdlib <import-path> <src-dir>
# List mode: print exported symbols to stdout (or -o file).
go run ./cmd/extract -list <src-dir>
A relative or . target in the default mode is canonicalized to its
real import path by go list, so extract . run inside a package
directory emits the correct import and map key.
Flags:
| Flag | Purpose |
|---|---|
-stdlib | Generate an mvm stdlib binding file. Output path is ./core/<file>.go or ./ext/<file>.go based on the Core map in cmd/extract/categories.go. |
-list | Print a raw <kind> <name> symbol listing of <src-dir> instead of generating a binding. |
-o <path> | Output file (default stdout); ignored when -stdlib is set. |
-pkg <name> | Package clause for the default-mode bindings file (default main). |
-goos <os> | Target GOOS for build-constraint filtering. Used for syscall. |
-goarch <arch> | Target GOARCH. Used together with -goos. |
Internal design
All three modes share extract() for symbol discovery and
go/format.Source for output. The diagram below shows the -stdlib
path (runGen); the default path (runValues) differs only in the
emitted file shape.
flowchart LR
src["GOROOT/src/<pkg>"] --> ex["extract()"]
ex -->|reflect-bindable\nsymbols| rg["runGen()"]
rg --> buf["bytes.Buffer"]
buf --> fmt["go/format.Source"]
fmt --> file["stdlib/{core,ext}/<pkg>.go"]
Symbol extraction (extract)
extract() parses the package directory through mvm's own
goparser, with the build context optionally pinned via -goos /
-goarch. Symbols are filtered:
- Exported only: unexported names skipped.
- No nested scopes or generic instantiations: names containing
/,., or#are skipped (they cannot be expressed as a singlereflect.ValueOf). - Untyped-int constants over
int64: wrapped asuint64(...)to avoidreflect.ValueOf(untyped int constant) overflows int. Named types are deliberately not wrapped, so bindings preserve e.g.time.Durationrather than collapsing it toint64.
Output is grouped by symbol.Kind (Const, Var, Type, Func)
and sorted within each group for deterministic generation.
Code generation (runGen)
runGen writes through a bytes.Buffer, then runs go/format.Source
on the buffered text and writes the formatted bytes to the target
file. This makes generated output gofmt-clean by construction.
No post-pass gofmt -w is needed.
The emitted file structure:
//go:build <tag> // optional, from BuildTags or -goos/-goarch
// Code generated by cmd/extract; DO NOT EDIT.
package <core|ext>
import (
"<importPath>"
"reflect"
"github.com/mvm-sh/mvm/stdlib"
)
func init() {
stdlib.Values["<importPath>"] = map[string]reflect.Value{
"Sym": reflect.ValueOf(pkg.Sym),
...
}
}
Packages that expose only generic symbols produce a stub file with just the package clause. Kept so bindings appear automatically once the package gains non-generic exports.
Default mode (runValues)
runValues resolves the target import path, runs the same extract(),
and renders a standalone binding map through buildValuesFile (again
go/format.Source-clean by construction). The emitted file structure:
// Code generated by cmd/extract; DO NOT EDIT.
package <-pkg, default main>
import (
"<importPath>"
"reflect"
)
var <alias>Values = map[string]map[string]reflect.Value{
"<importPath>": {
"Sym": reflect.ValueOf(pkg.Sym),
...
},
}
The outer key is the canonical import path (so interpreted
import "<importPath>" resolves), and <alias> is the package's
declared name (not the path's last element, so versioned paths like
gopkg.in/yaml.v3 bind as yaml). resolvePkg obtains both from
go list -f '{{.ImportPath}}\t{{.Dir}}\t{{.Name}}', rejecting a pattern
that matches multiple packages. When an explicit [dir] is given (for
packages go list cannot resolve) the import path is taken as supplied
and the name comes from go/build.ImportDir, which honors build
constraints and parses the clause (so a //go:build ignore package main generator file is skipped and a trailing package-clause comment is
not mistaken for part of the name).
The same value/var/type shape as runGen is used: consts and funcs by
value, vars by address (&pkg.V), types as a typed nil pointer
((*pkg.T)(nil)). A package with no exported symbols omits its own
import to keep the file compilable.
categories.go
Top-level maps:
Core: set of import paths routed tostdlib/core/. Everything not listed goes tostdlib/ext/. The criterion is "pure-compute and light on transitive deps" (see ADR-013).BuildTags: optional whole-package//go:buildexpression. Gatesruntime/cgobehindcgo(soGOOS=js GOARCH=wasmstill links) and whole packages that only build on newer Go (crypto/hpke,testing/cryptotestongo1.26;testing/synctestongo1.25).SymbolBuildTags: per-symbol additions in newer Go releases, keyed by import path then//go:buildexpression. Those symbols are split out of the base file into a supplement<pkg>_<suffix>.go(e.g.crypto_go126.go) guarded by the tag, so the base files build on the oldest Go release named ingo.mod'sgodirective (1.24). Hand-maintained from$GOROOT/api/go1.<N>.txt; revisit on every toolchain bump.tagFileSuffixmaps a tag to its filename suffix.
Dependencies
github.com/mvm-sh/mvm/goparser: used to parse the package source with the same build-tag handling as the interpreter itself, soextractsees only files that mvm would actually run.github.com/mvm-sh/mvm/symbol:Kind,Symbol.github.com/mvm-sh/mvm/lang/golang: Go language spec for the parser.go/constant: for the over-int64const detection.go/format: gofmt the buffered output before writing.go/build:ImportDirresolves the declared package name from an explicit[dir]while honoring build constraints (default mode).os/exec: runsgo listto canonicalize an import path and locate its source (default mode).
Open questions / TODOs
- Generated stubs for generics-only packages (
crypto/hkdf,crypto/pbkdf2,unique,weak) are kept around to auto-populate on future Go releases. If a release adds non-generic exports, no manual action should be needed beyondmake generate.