feat(fnode): --go-replace and --module for BEGINDUMP-import use cases
Two CLI knobs that together let PRG #pragma BEGINDUMP blocks import
private Go modules and their internal/* packages:
--go-replace pkg=path
Adds `require pkg v0.0.0` + `replace pkg => path` to the temp
go.mod, so `go mod tidy` doesn't try (and fail) to fetch a
module that lives only on local disk or behind a private
forge (Gitea, etc.).
--module <name>
Overrides the temp-build module name (default fnode_build).
Use when the BEGINDUMP block needs to reach into another
module's internal/ packages — Go's internal-visibility rule
requires the importer to share a parent path with the
importee, so the build module needs to live somewhere under
that parent.
Worked example (solmade integration PoC):
fnode build /tmp/poc_dartapi.prg \\
--go-replace gitea.fivego.org/kwon_ai/solmade=/Users/charleskwon/solmade \\
--module gitea.fivego.org/kwon_ai/solmade/_fnode_build \\
-o poc_dart
A 4-line BEGINDUMP block now imports
"gitea.fivego.org/kwon_ai/solmade/internal/dartapi", links its
AllAliases() function, and returns the real count (3) at run time.
Single 23 MB Go binary, no Node, no FFI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"five/compiler/analyzer"
|
"five/compiler/analyzer"
|
||||||
@@ -68,8 +69,8 @@ func printUsage() {
|
|||||||
fmt.Fprintln(os.Stderr, version)
|
fmt.Fprintln(os.Stderr, version)
|
||||||
fmt.Fprintln(os.Stderr, "")
|
fmt.Fprintln(os.Stderr, "")
|
||||||
fmt.Fprintln(os.Stderr, "Usage:")
|
fmt.Fprintln(os.Stderr, "Usage:")
|
||||||
fmt.Fprintln(os.Stderr, " fnode build <file.prg>... [-o out] [-I dir]... [--rtl pkg]...")
|
fmt.Fprintln(os.Stderr, " fnode build <file.prg>... [-o out] [-I dir]... [--rtl pkg]... [--go-replace pkg=path]...")
|
||||||
fmt.Fprintln(os.Stderr, " fnode run <file.prg> [-I dir]... [--rtl pkg]...")
|
fmt.Fprintln(os.Stderr, " fnode run <file.prg> [-I dir]... [--rtl pkg]... [--go-replace pkg=path]...")
|
||||||
fmt.Fprintln(os.Stderr, " fnode version")
|
fmt.Fprintln(os.Stderr, " fnode version")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,10 +79,24 @@ type buildOpts struct {
|
|||||||
output string
|
output string
|
||||||
includes []string
|
includes []string
|
||||||
rtlPkgs []string
|
rtlPkgs []string
|
||||||
|
// goReplace maps a Go module path to a local filesystem path that
|
||||||
|
// gets emitted as `replace <path> => <local>` in the temp go.mod.
|
||||||
|
// Lets PRG #pragma BEGINDUMP blocks import private modules that
|
||||||
|
// `go mod tidy` would otherwise fail to fetch (e.g. a sibling
|
||||||
|
// project living outside any module proxy).
|
||||||
|
goReplace map[string]string
|
||||||
|
// goModule overrides the default temp-build module name
|
||||||
|
// ("fnode_build"). Set this to a path inside an existing module
|
||||||
|
// when the BEGINDUMP block needs to import that module's
|
||||||
|
// internal/* packages — Go's internal-visibility rule only lets
|
||||||
|
// callers whose own module path lives under the same parent see
|
||||||
|
// internal/.
|
||||||
|
// --module gitea.fivego.org/kwon_ai/solmade/_fnode_build
|
||||||
|
goModule string
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCommon(args []string) buildOpts {
|
func parseCommon(args []string) buildOpts {
|
||||||
var o buildOpts
|
o := buildOpts{goReplace: map[string]string{}}
|
||||||
for i := 0; i < len(args); i++ {
|
for i := 0; i < len(args); i++ {
|
||||||
a := args[i]
|
a := args[i]
|
||||||
switch {
|
switch {
|
||||||
@@ -96,6 +111,18 @@ func parseCommon(args []string) buildOpts {
|
|||||||
case a == "--rtl" && i+1 < len(args):
|
case a == "--rtl" && i+1 < len(args):
|
||||||
o.rtlPkgs = append(o.rtlPkgs, args[i+1])
|
o.rtlPkgs = append(o.rtlPkgs, args[i+1])
|
||||||
i++
|
i++
|
||||||
|
case a == "--go-replace" && i+1 < len(args):
|
||||||
|
// --go-replace gitea.fivego.org/kwon_ai/solmade=/abs/path
|
||||||
|
spec := args[i+1]
|
||||||
|
i++
|
||||||
|
eq := strings.IndexByte(spec, '=')
|
||||||
|
if eq <= 0 || eq == len(spec)-1 {
|
||||||
|
fatal("--go-replace expects pkg=path, got: " + spec)
|
||||||
|
}
|
||||||
|
o.goReplace[spec[:eq]] = spec[eq+1:]
|
||||||
|
case a == "--module" && i+1 < len(args):
|
||||||
|
o.goModule = args[i+1]
|
||||||
|
i++
|
||||||
default:
|
default:
|
||||||
o.prgFiles = append(o.prgFiles, a)
|
o.prgFiles = append(o.prgFiles, a)
|
||||||
}
|
}
|
||||||
@@ -124,7 +151,7 @@ func cmdBuild(args []string) {
|
|||||||
|
|
||||||
emitGeneratedSources(tmpDir, o)
|
emitGeneratedSources(tmpDir, o)
|
||||||
writeRootGo(tmpDir, o.rtlPkgs)
|
writeRootGo(tmpDir, o.rtlPkgs)
|
||||||
writeGoMod(tmpDir)
|
writeGoMod(tmpDir, o.goReplace, o.goModule)
|
||||||
|
|
||||||
runGo(tmpDir, "mod", "tidy")
|
runGo(tmpDir, "mod", "tidy")
|
||||||
runGo(tmpDir, "build", "-o", absOut, ".")
|
runGo(tmpDir, "build", "-o", absOut, ".")
|
||||||
@@ -141,7 +168,7 @@ func cmdRun(args []string) {
|
|||||||
|
|
||||||
emitGeneratedSources(tmpDir, o)
|
emitGeneratedSources(tmpDir, o)
|
||||||
writeRootGo(tmpDir, o.rtlPkgs)
|
writeRootGo(tmpDir, o.rtlPkgs)
|
||||||
writeGoMod(tmpDir)
|
writeGoMod(tmpDir, o.goReplace, o.goModule)
|
||||||
|
|
||||||
runGo(tmpDir, "mod", "tidy")
|
runGo(tmpDir, "mod", "tidy")
|
||||||
runGoInteractive(tmpDir, "run", ".")
|
runGoInteractive(tmpDir, "run", ".")
|
||||||
@@ -288,10 +315,14 @@ func writeRootGo(tmpDir string, rtlPkgs []string) {
|
|||||||
writeFile(filepath.Join(tmpDir, "fnode_rtl.go"), sb.String())
|
writeFile(filepath.Join(tmpDir, "fnode_rtl.go"), sb.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeGoMod(tmpDir string) {
|
func writeGoMod(tmpDir string, extraReplace map[string]string, modName string) {
|
||||||
five := mustAbs(fiveRoot())
|
five := mustAbs(fiveRoot())
|
||||||
fnode := mustAbs(fnodeRoot())
|
fnode := mustAbs(fnodeRoot())
|
||||||
mod := fmt.Sprintf(`module fnode_build
|
if modName == "" {
|
||||||
|
modName = "fnode_build"
|
||||||
|
}
|
||||||
|
var sb strings.Builder
|
||||||
|
fmt.Fprintf(&sb, `module %s
|
||||||
|
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
@@ -303,8 +334,22 @@ require (
|
|||||||
replace five => %s
|
replace five => %s
|
||||||
|
|
||||||
replace fivenode_go => %s
|
replace fivenode_go => %s
|
||||||
`, five, fnode)
|
`, modName, five, fnode)
|
||||||
writeFile(filepath.Join(tmpDir, "go.mod"), mod)
|
// Sorted iteration for stable go.mod output.
|
||||||
|
keys := make([]string, 0, len(extraReplace))
|
||||||
|
for k := range extraReplace {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, pkg := range keys {
|
||||||
|
path := extraReplace[pkg]
|
||||||
|
if !filepath.IsAbs(path) {
|
||||||
|
path = mustAbs(path)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&sb, "\nrequire %s v0.0.0\n", pkg)
|
||||||
|
fmt.Fprintf(&sb, "replace %s => %s\n", pkg, path)
|
||||||
|
}
|
||||||
|
writeFile(filepath.Join(tmpDir, "go.mod"), sb.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustAbs(p string) string {
|
func mustAbs(p string) string {
|
||||||
|
|||||||
Reference in New Issue
Block a user