diff --git a/cmd/neva/main.go b/cmd/neva/main.go index 36c5d550..c0ae91b9 100644 --- a/cmd/neva/main.go +++ b/cmd/neva/main.go @@ -20,7 +20,7 @@ import ( ) func main() { - wd, err := os.Getwd() + workdir, err := os.Getwd() if err != nil { panic(err) } @@ -53,9 +53,7 @@ func main() { &desugarer, analyzer, irgen, - native.NewBackend( - golangBackend, - ), + native.NewBackend(golangBackend), ) wasmCompiler := compiler.New( @@ -64,9 +62,7 @@ func main() { &desugarer, analyzer, irgen, - wasm.NewBackend( - golangBackend, - ), + wasm.NewBackend(golangBackend), ) jsonCompiler := compiler.New( @@ -89,7 +85,7 @@ func main() { // command-line app that can compile and interpret neva code app := cli.NewApp( - wd, + workdir, bldr, goCompiler, nativeCompiler, diff --git a/docs/tutorial.md b/docs/tutorial.md index 201b9d13..46929035 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -129,6 +129,8 @@ This will produce an `output` file in the directory where neva-cli was executed, Once again you should see `Hello, World!`. +> Execute `neva build --help` to learn more - how to compile to Go, WASM or how to do cross-compilation e.g. compile linux binaries in windows. + ## Core Concepts ### Components diff --git a/internal/cli/build.go b/internal/cli/build.go index 73f13f0d..45df3776 100644 --- a/internal/cli/build.go +++ b/internal/cli/build.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "os" "github.com/nevalang/neva/internal/compiler" @@ -10,15 +11,15 @@ import ( func newBuildCmd( workdir string, - goc compiler.Compiler, - nativec compiler.Compiler, - wasmc compiler.Compiler, - jsonc compiler.Compiler, - dotc compiler.Compiler, + compilerToGo compiler.Compiler, + compilerToNative compiler.Compiler, + compilerToWASM compiler.Compiler, + compilerToJSON compiler.Compiler, + compilerToDOT compiler.Compiler, ) *cli.Command { return &cli.Command{ Name: "build", - Usage: "Build neva program from source code", + Usage: "Generate target platform code from neva program", Args: true, Flags: []cli.Flag{ &cli.StringFlag{ @@ -31,7 +32,7 @@ func newBuildCmd( }, &cli.StringFlag{ Name: "target", - Usage: "Target platform for build", + Usage: "Target platform for build (options: go, wasm, native, json, dot). For 'native' target, 'target-os' and 'target-arch' flags can be used, but if used, they must be used together.", Action: func(ctx *cli.Context, s string) error { switch s { case "go", "wasm", "native", "json", "dot": @@ -40,50 +41,101 @@ func newBuildCmd( return fmt.Errorf("Unknown target %s", s) }, }, + &cli.StringFlag{ + Name: "target-os", + Usage: "Target operating system for native build. See 'neva osarch' for supported combinations. Only supported for native target. Not needed if building for the current platform. Must be combined properly with 'target-arch'.", + }, + &cli.StringFlag{ + Name: "target-arch", + Usage: "Target architecture for native build. See 'neva osarch' for supported combinations. Only supported for native target. Not needed if building for the current platform. Must be combined properly with 'target-os'.", + }, }, ArgsUsage: "Provide path to main package", Action: func(cliCtx *cli.Context) error { + var target string + if cliCtx.IsSet("target") { + target = cliCtx.String("target") + } else { + target = "native" + } + + switch target { + case "go", "wasm", "json", "dot", "native": + default: + return fmt.Errorf("Unknown target %s", target) + } + + var targetOS, targetArch string + if cliCtx.IsSet("target-os") { + targetOS = cliCtx.String("target-os") + } + if cliCtx.IsSet("target-arch") { + targetArch = cliCtx.String("target-arch") + } + if (targetOS != "" && targetArch == "") || (targetOS == "" && targetArch != "") { + return fmt.Errorf("target-os and target-arch must be set together") + } + if target != "native" && targetOS != "" { + return fmt.Errorf("target-os and target-arch are only supported when target is native") + } + mainPkg, err := mainPkgPathFromArgs(cliCtx) if err != nil { return err } - output := workdir + outputDirPath := workdir if cliCtx.IsSet("output") { - output = cliCtx.String("output") - } - - var target string - if cliCtx.IsSet("target") { - target = cliCtx.String("target") + outputDirPath = cliCtx.String("output") } - var trace bool + var isTraceEnabled bool if cliCtx.IsSet("trace") { - trace = true + isTraceEnabled = true } - input := compiler.CompilerInput{ + compilerInput := compiler.CompilerInput{ Main: mainPkg, - Output: output, - Trace: trace, + Output: outputDirPath, + Trace: isTraceEnabled, } - var c compiler.Compiler + var compilerToUse compiler.Compiler switch target { case "go": - c = goc + compilerToUse = compilerToGo case "wasm": - c = wasmc + compilerToUse = compilerToWASM case "json": - c = jsonc + compilerToUse = compilerToJSON case "dot": - c = dotc - default: - c = nativec + compilerToUse = compilerToDOT + case "native": + compilerToUse = compilerToNative + } + + if targetOS != "" { + prevGOOS := os.Getenv("GOOS") + prevGOARCH := os.Getenv("GOARCH") + + if err := os.Setenv("GOOS", targetOS); err != nil { + return fmt.Errorf("set GOOS: %w", err) + } + if err := os.Setenv("GOARCH", targetArch); err != nil { + return fmt.Errorf("set GOARCH: %w", err) + } + + defer func() { + if err := os.Setenv("GOOS", prevGOOS); err != nil { + panic(err) + } + if err := os.Setenv("GOARCH", prevGOARCH); err != nil { + panic(err) + } + }() } - return c.Compile(cliCtx.Context, input) + return compilerToUse.Compile(cliCtx.Context, compilerInput) }, } } diff --git a/internal/cli/cli.go b/internal/cli/cli.go index afc1bbc3..b1917978 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -33,6 +33,7 @@ func NewApp( newGetCmd(workdir, bldr), newRunCmd(workdir, nativec), newBuildCmd(workdir, goc, nativec, wasmc, jsonc, dotc), + newOSArchCmd(), }, } } diff --git a/internal/cli/osarch.go b/internal/cli/osarch.go new file mode 100644 index 00000000..286363fd --- /dev/null +++ b/internal/cli/osarch.go @@ -0,0 +1,34 @@ +package cli + +import ( + "fmt" + "os/exec" + "strings" + + cli "github.com/urfave/cli/v2" +) + +func newOSArchCmd() *cli.Command { + return &cli.Command{ + Name: "osarch", + Usage: "List supported OS/architecture combinations for native target", + Action: func(cliCtx *cli.Context) error { + cmd := exec.Command("go", "tool", "dist", "list") + output, err := cmd.Output() + if err != nil { + return fmt.Errorf("failed to execute go tool dist list: %w", err) + } + + fmt.Println("Supported OS/architecture combinations for native target:") + fmt.Println("(use these values for --target-os and --target-arch flags when cross-compiling)") + fmt.Println() + + platforms := strings.Split(strings.TrimSpace(string(output)), "\n") + for _, platform := range platforms { + fmt.Println(platform) + } + + return nil + }, + } +}