Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 79 additions & 34 deletions cmd/gova/build.go
Original file line number Diff line number Diff line change
@@ -1,50 +1,95 @@
package main

import (
"flag"
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"

"github.com/nv404/gova/internal/devserver"
"github.com/nv404/gova/internal/config"
"github.com/nv404/gova/internal/utils"
"github.com/urfave/cli/v3"
)

func cmdBuild(args []string, stdout, stderr io.Writer) int {
fs := flag.NewFlagSet("build", flag.ContinueOnError)
fs.SetOutput(stderr)
out := fs.String("o", "", "output path (default ./bin/<pkg-basename>)")
pkg, _, err := parseFlags("build", fs, args)
if err != nil {
return 2
var buildCmd = &cli.Command{
Name: "build",
Description: "Build a gova application",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "mode",
Required: true,
Value: "gova-dev",
Aliases: []string{"m"},
Config: cli.StringConfig{
TrimSpace: true,
},
DefaultText: "-m gova-dev",
Usage: "-m dev",
},
},
Action: func(ctx context.Context, c *cli.Command) error {
mode := c.String("mode")

cnf, err := config.GetConfig()
if err != nil {
return err
}

if err := buildApplication(ctx, mode, cnf); err != nil {
return err
}

return nil
},
}

// buildApplication compiles the Go package defined in cfg using the build
// configuration for the given mode. It resolves the output binary path,
// sanitizes user-provided build flags, injects environment variables from
// EnvFiles and Env in that order, and invokes `go build`.
//
// The output binary is placed at OutputDir/BinaryName relative to the
// current working directory.
//
// Environment variables are layered as follows:
// - The current process environment (os.Environ) as the base
// - EnvFiles merged in order, later files taking precedence
// - Inline Env values taking final precedence
func buildApplication(ctx context.Context, mode string, cfg config.Config) error {
m, ok := cfg.Modes[mode]
if !ok {
return fmt.Errorf("unknown mode %q", mode)
}

wd, err := os.Getwd()
if err != nil {
fmt.Fprintln(stderr, err)
return 1
return err
}
output := *out
if output == "" {
name := filepath.Base(wd)
if pkg != "." {
name = filepath.Base(pkg)
}
if runtime.GOOS == "windows" {
name += ".exe"
}
output = filepath.Join(wd, "bin", name)
if err := os.MkdirAll(filepath.Dir(output), 0o755); err != nil {
fmt.Fprintln(stderr, err)
return 1
}

outputDir := utils.NormalizePath(cfg.OutputDir)
output := filepath.Join(wd, outputDir, utils.BinaryName(cfg.Name))
path := strings.Join([]string{".", utils.NormalizePath(cfg.Package)}, string(filepath.Separator))

flags := append([]string{"build"}, utils.SanitizeBuildFlags(m.BuildFlags)...)
flags = append(flags, "-o", output, path)

build := exec.CommandContext(ctx, "go", flags...)

env, err := utils.ResolveEnv(m)
if err != nil {
return err
}
ctx, cancel := signalContext()
defer cancel()
if err := devserver.Build(ctx, wd, pkg, output); err != nil {
fmt.Fprintln(stderr, err)
return 1

build.Env = env
build.Stdin = os.Stdin
build.Stdout = os.Stdout
build.Stderr = os.Stderr

if err := build.Run(); err != nil {
return fmt.Errorf("go build failed: %w", err)
}
fmt.Fprintf(stdout, "gova build: wrote %s\n", output)
return 0

return nil
}
Loading