// This file is auto-generated by alef — DO NOT EDIT.
// cmd/setup downloads the platform-specific native FFI library from GitHub
// releases into a versioned per-user cache, verifies its SHA-256 checksum,
// and writes a machine-local cgo link shim into a consumer package so that
// `go build`/`go test` can find the native library without shipping it
// inside the published Go module (module zips only contain the git tag's
// files, and native libraries are far too large — and too platform-specific
// — to commit).
//
// Usage:
//
// go run {{ module_path }}/cmd/setup # write the shim into ./
// go run {{ module_path }}/cmd/setup -dir path/to/pkg # write the shim into a specific package
// go run {{ module_path }}/cmd/setup -lib-dir .lib # vendor mode: extract natives, skip the shim
// go run {{ module_path }}/cmd/setup -print-env # print CGO_CFLAGS/CGO_LDFLAGS exports
// go run {{ module_path }}/cmd/setup -force # force re-download even if cached
//
// It lives in its own `package main` subdirectory, so it is never linked into
// the binding library and needs no build-constraint guard — keeping it
// guard-free lets consumers (and the test-app runner) execute it directly.
package main
import (
"archive/tar"
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/hex"
"flag"
"fmt"
"go/parser"
"go/token"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
const (
moduleVersion = "{{ crate_version }}"
repoURL = "{{ repo_url }}"
assetPrefix = "{{ asset_prefix }}"
ffiLibName = "{{ ffi_lib_name }}"
crateName = "{{ crate_name }}"
shimFilename = "{{ shim_filename }}"
versionIdent = "{{ version_ident }}"
envOverrideVar = "{{ env_override_var }}"
bindingImportPath = "{{ module_path }}"
bindingImportName = "{{ go_ident_crate_name }}nativesetup"
)
// options holds the parsed CLI flags.
type options struct {
dir string
libDir string
printEnv bool
force bool
}
func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
func run() error {
opts := parseFlags()
platform, err := platformDir()
if err != nil {
return fmt.Errorf("determine platform: %w", err)
}
cacheDir, err := nativeCacheDir(platform)
if err != nil {
return fmt.Errorf("determine native cache directory: %w", err)
}
libPath := filepath.Join(cacheDir, libFilename(ffiLibName, runtime.GOOS))
if opts.force || !fileExists(libPath) {
if err := downloadAndVerify(cacheDir, platform); err != nil {
return fmt.Errorf("download native library: %w", err)
}
}
if !fileExists(libPath) {
return fmt.Errorf("native library missing after download/verification: %s", libPath)
}
switch {
case opts.libDir != "":
return vendorMode(cacheDir, opts.libDir, platform)
case opts.printEnv:
return printEnvMode(cacheDir)
default:
return shimMode(cacheDir, opts.dir)
}
}
func parseFlags() options {
var opts options
flag.StringVar(&opts.dir, "dir", ".", "directory to write the cgo link shim into")
flag.StringVar(&opts.libDir, "lib-dir", "",
"extract natives into <dir>/<platform>/ and skip writing the shim (vendor / go generate mode)")
flag.BoolVar(&opts.printEnv, "print-env", false,
"print CGO_CFLAGS/CGO_LDFLAGS shell exports instead of writing a shim")
flag.BoolVar(&opts.force, "force", false, "force re-download even if the library is already cached")
flag.Parse()
return opts
}
// platformDir returns the alef platform directory name (e.g. "macos-arm64",
// "linux-x86_64", "windows-x86_64") matching the layout that ships in the
// `<assetPrefix>-go-<os>-<arch>.tar.gz` release assets and the LDFLAGS paths
// baked into the binding package's cgo preamble.
func platformDir() (string, error) {
osName := runtime.GOOS
if osName == "darwin" {
osName = "macos"
}
archName := runtime.GOARCH
switch runtime.GOARCH {
case "amd64":
archName = "x86_64"
case "arm64":
// macOS arm64 stays "arm64" (alef's platform naming special-cases it);
// all other platforms use "aarch64".
if runtime.GOOS != "darwin" {
archName = "aarch64"
}
}
if osName == "" || archName == "" {
return "", fmt.Errorf("unsupported platform: GOOS=%q GOARCH=%q", runtime.GOOS, runtime.GOARCH)
}
return fmt.Sprintf("%s-%s", osName, archName), nil
}
func libFilename(libName, goos string) string {
switch goos {
case "windows":
return libName + ".dll"
case "darwin":
return "lib" + libName + ".dylib"
default:
return "lib" + libName + ".so"
}
}
// nativeCacheDir returns the versioned per-platform cache directory:
// `os.UserCacheDir()/<crateName>/go/<moduleVersion>/<platform>/`.
func nativeCacheDir(platform string) (string, error) {
base, err := os.UserCacheDir()
if err != nil {
return "", fmt.Errorf("resolve user cache dir: %w", err)
}
return filepath.Join(base, crateName, "go", moduleVersion, platform), nil
}
func fileExists(path string) bool {
info, err := os.Stat(path)
return err == nil && !info.IsDir()
}
// baseDownloadURL returns the release base URL, honoring the
// `{{ env_override_var }}` override env var for mirrors, air-gapped
// installs, or local testing.
func baseDownloadURL() string {
if override := os.Getenv(envOverrideVar); override != "" {
return strings.TrimSuffix(override, "/")
}
version := strings.TrimPrefix(moduleVersion, "v")
return fmt.Sprintf("%s/releases/download/v%s", strings.TrimSuffix(repoURL, "/"), version)
}
// downloadAndVerify downloads the platform tarball, verifies it against its
// required `.sha256` sidecar, and extracts it into cacheDir.
func downloadAndVerify(cacheDir, platform string) error {
assetName := fmt.Sprintf("%s-go-%s.tar.gz", assetPrefix, platform)
baseURL := baseDownloadURL()
assetURL := fmt.Sprintf("%s/%s", baseURL, assetName)
sumURL := assetURL + ".sha256"
expectedSum, err := downloadChecksum(sumURL)
if err != nil {
return err
}
body, err := downloadBytes(assetURL)
if err != nil {
return err
}
actualSum := sha256Hex(body)
if !strings.EqualFold(actualSum, expectedSum) {
return fmt.Errorf(
"checksum mismatch for %s: expected %s, got %s — the download is corrupted, please retry",
assetURL, expectedSum, actualSum,
)
}
if err := os.MkdirAll(cacheDir, 0o755); err != nil {
return fmt.Errorf("mkdir %s: %w", cacheDir, err)
}
if err := extractTarGz(bytes.NewReader(body), cacheDir); err != nil {
return fmt.Errorf("extract %s: %w", assetName, err)
}
return nil
}
func downloadChecksum(sumURL string) (string, error) {
body, err := downloadBytes(sumURL)
if err != nil {
return "", fmt.Errorf("download sha256 sidecar %s: %w (the .sha256 sidecar is required)", sumURL, err)
}
fields := strings.Fields(string(body))
if len(fields) == 0 {
return "", fmt.Errorf("sha256 sidecar %s is empty", sumURL)
}
return strings.ToLower(fields[0]), nil
}
func downloadBytes(url string) ([]byte, error) {
resp, err := http.Get(url) //nolint:gosec,noctx // release asset URL, not user-controlled input
if err != nil {
return nil, fmt.Errorf("GET %s: %w", url, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("GET %s: HTTP %d", url, resp.StatusCode)
}
return io.ReadAll(resp.Body)
}
func sha256Hex(data []byte) string {
sum := sha256.Sum256(data)
return hex.EncodeToString(sum[:])
}
// extractTarGz extracts a gzip-compressed tarball into dstDir, flattening
// any leading directory components from each entry's name.
func extractTarGz(src io.Reader, dstDir string) error {
gzr, err := gzip.NewReader(src)
if err != nil {
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
targetPath := filepath.Join(dstDir, filepath.Base(header.Name))
switch header.Typeflag {
case tar.TypeReg:
if err := writeExtractedFile(targetPath, tr, header.Mode); err != nil {
return err
}
case tar.TypeDir:
if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil {
return err
}
}
}
return nil
}
func writeExtractedFile(targetPath string, r io.Reader, mode int64) error {
f, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(mode))
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, r)
return err
}
// vendorMode extracts natives into <libDir>/<platform>/ for a writable
// checkout (used by `go generate` via generate.go) and skips the shim
// entirely — the checkout's own cgo preamble already points at
// `${SRCDIR}/.lib/<platform>`.
func vendorMode(cacheDir, libDir, platform string) error {
dest := filepath.Join(libDir, platform)
if err := os.MkdirAll(dest, 0o755); err != nil {
return fmt.Errorf("mkdir %s: %w", dest, err)
}
entries, err := os.ReadDir(cacheDir)
if err != nil {
return fmt.Errorf("read cache dir %s: %w", cacheDir, err)
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
if err := copyFile(filepath.Join(cacheDir, entry.Name()), filepath.Join(dest, entry.Name())); err != nil {
return fmt.Errorf("vendor %s: %w", entry.Name(), err)
}
}
libPath := filepath.Join(dest, libFilename(ffiLibName, runtime.GOOS))
if !fileExists(libPath) {
return fmt.Errorf("vendored library missing after copy: %s", libPath)
}
fmt.Printf("Vendored native library into %s\n", dest)
return nil
}
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}
// printEnvMode prints shell export lines for CGO_CFLAGS/CGO_LDFLAGS pointing
// at the cache directory, for consumers that prefer to wire cgo flags
// themselves instead of using the generated shim.
func printEnvMode(cacheDir string) error {
fmt.Printf("export CGO_LDFLAGS=\"-L%s -Wl,-rpath,%s -l%s\"\n", cacheDir, cacheDir, ffiLibName)
return nil
}
// shimMode detects the target package in dir and writes the machine-local
// cgo link shim into it.
func shimMode(cacheDir, dir string) error {
pkg := detectPackageName(dir)
shimPath := filepath.Join(dir, shimFilename)
content, err := renderShim(pkg, cacheDir, dir)
if err != nil {
return fmt.Errorf("render shim: %w", err)
}
if err := os.WriteFile(shimPath, []byte(content), 0o644); err != nil {
return fmt.Errorf("write %s: %w", shimPath, err)
}
if !fileExists(shimPath) {
return fmt.Errorf("shim was not written: %s", shimPath)
}
if runtime.GOOS == "windows" {
if err := copyWindowsDLL(cacheDir, dir); err != nil {
return fmt.Errorf("copy windows DLL: %w", err)
}
}
fmt.Printf("Wrote %s (package %s)\n", shimPath, pkg)
fmt.Println("Next steps:")
fmt.Printf(" - add %q to your .gitignore — it is machine-local and must not be committed\n", shimFilename)
fmt.Println(" - re-run `cmd/setup` after upgrading this module's version")
return nil
}
// detectPackageName determines the Go package name of dir: first via `go
// list`, then by parsing an existing .go file with go/parser, finally
// falling back to "main".
func detectPackageName(dir string) string {
if pkg, err := goListPackageName(dir); err == nil && pkg != "" {
return pkg
}
if pkg, err := parsePackageNameFromFile(dir); err == nil && pkg != "" {
return pkg
}
return "main"
}
func goListPackageName(dir string) (string, error) {
cmd := exec.Command("go", "list", "-f", "{{ "{{" }}.Name{{ "}}" }}")
cmd.Dir = dir
out, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
}
func parsePackageNameFromFile(dir string) (string, error) {
entries, err := os.ReadDir(dir)
if err != nil {
return "", err
}
fset := token.NewFileSet()
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
continue
}
path := filepath.Join(dir, entry.Name())
file, err := parser.ParseFile(fset, path, nil, parser.PackageClauseOnly)
if err != nil {
continue
}
if file.Name != nil && file.Name.Name != "" {
return file.Name.Name, nil
}
}
return "", fmt.Errorf("no parseable .go file found in %s", dir)
}
// renderShim renders the cgo link shim source. On windows the `-rpath`
// linker flag (not supported the same way) is omitted; copyWindowsDLL places
// the DLL next to dir instead, and callers are told to update PATH.
//
// If dir happens to resolve to the binding package's own directory (a
// self-import), the compiler reports that the normal way — as an import
// cycle — when the shim is built; that's clear enough on its own and doesn't
// need a bespoke check here (there's no reliable way to detect it from
// inside a running `go run` process: os.Getwd() reports the caller's working
// directory, not this package's own source location).
func renderShim(pkg, cacheDir, dir string) (string, error) {
var b strings.Builder
fmt.Fprintf(&b, "// Code generated by %s cmd/setup — machine-local, DO NOT COMMIT (add to .gitignore).\n", crateName)
fmt.Fprintf(&b, "package %s\n\n", pkg)
if runtime.GOOS == "windows" {
fmt.Fprint(&b, "/*\n")
fmt.Fprintf(&b, "#cgo LDFLAGS: -L%q\n", cacheDir)
fmt.Fprint(&b, "*/\n")
} else {
fmt.Fprint(&b, "/*\n")
fmt.Fprintf(&b, "#cgo LDFLAGS: -L%q -Wl,-rpath,%q\n", cacheDir, cacheDir)
fmt.Fprint(&b, "*/\n")
}
fmt.Fprint(&b, "import \"C\"\n\n")
fmt.Fprintf(&b, "import %s %q\n\n", bindingImportName, bindingImportPath)
fmt.Fprint(&b, "// Compile-time guard: fails after a module upgrade until setup is re-run.\n")
fmt.Fprintf(&b, "var _ = %s.RequireNativeSetup_%s\n", bindingImportName, versionIdent)
return b.String(), nil
}
// copyWindowsDLL copies the native DLL from cacheDir next to dir and prints
// PATH guidance, since windows LDFLAGS don't support an rpath-equivalent.
func copyWindowsDLL(cacheDir, dir string) error {
dllName := libFilename(ffiLibName, "windows")
src := filepath.Join(cacheDir, dllName)
dst := filepath.Join(dir, dllName)
if err := copyFile(src, dst); err != nil {
return fmt.Errorf("copy %s to %s: %w", src, dst, err)
}
fmt.Printf("Copied %s next to %s.\n", dllName, dir)
fmt.Println("On Windows, ensure this directory (or the cache directory below) is on PATH at runtime:")
fmt.Printf(" %s\n", cacheDir)
return nil
}