Mapp, Go-modul, binär, VS Code-sökvägar, dokumentation och visningsnamn följer nu repots namn på Gitea. Arbetsnamnet jp-subway-pussel finns kvar som historik i spelbeskrivningen §15. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
206 lines
5.4 KiB
Go
206 lines
5.4 KiB
Go
// Byggverktyg: `go run ./tools/build [flaggor]`
|
||
//
|
||
// Skrivet i Go så att samma kommando fungerar på Linux och Windows utan
|
||
// make, bash eller andra extra verktyg. Bygger binären med rätt flaggor för
|
||
// debug/release, stämplar version + läge via -ldflags och kopierar
|
||
// resources/ bredvid binären.
|
||
//
|
||
// debug -> build/debug/<binär> + resources/
|
||
// release -> build/release/<os>-<arch>/<binär> + resources/
|
||
package main
|
||
|
||
import (
|
||
"flag"
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"runtime"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
appName = "metro-puzzle-jp"
|
||
mainPkg = "./cmd/" + appName
|
||
)
|
||
|
||
func main() {
|
||
mode := flag.String("mode", "debug", "debug | release")
|
||
goos := flag.String("os", runtime.GOOS, "målplattform: linux | windows")
|
||
goarch := flag.String("arch", runtime.GOARCH, "arkitektur: amd64 | arm64")
|
||
run := flag.Bool("run", false, "starta binären efter bygget (kräver att os/arch matchar den här datorn)")
|
||
flag.Parse()
|
||
|
||
if *mode != "debug" && *mode != "release" {
|
||
fatal("-mode måste vara debug eller release")
|
||
}
|
||
switch *goos {
|
||
case "linux", "windows":
|
||
case "darwin":
|
||
fatal("macOS kräver cgo och måste byggas på en Mac (`go build` fungerar där, men inte korsbygge härifrån)")
|
||
default:
|
||
fatal("stöd saknas för -os " + *goos)
|
||
}
|
||
if *goos == "linux" && (*goos != runtime.GOOS || *goarch != runtime.GOARCH) {
|
||
fatal("korsbygge till linux/" + *goarch + " kräver en C-korskompilator (cgo); bygg på målmaskinen i stället")
|
||
}
|
||
|
||
root := repoRoot()
|
||
module := modulePath(root)
|
||
version := gitVersion(root)
|
||
|
||
outDir := filepath.Join(root, "build", "debug")
|
||
if *mode == "release" {
|
||
outDir = filepath.Join(root, "build", "release", *goos+"-"+*goarch)
|
||
}
|
||
exe := appName
|
||
if *goos == "windows" {
|
||
exe += ".exe"
|
||
}
|
||
exePath := filepath.Join(outDir, exe)
|
||
must(os.MkdirAll(outDir, 0o755))
|
||
|
||
ldflags := []string{
|
||
"-X", module + "/internal/buildinfo.Version=" + version,
|
||
"-X", module + "/internal/buildinfo.Mode=" + *mode,
|
||
}
|
||
args := []string{"build", "-o", exePath}
|
||
if *mode == "release" {
|
||
ldflags = append(ldflags, "-s", "-w") // strippa symboler/debuginfo
|
||
if *goos == "windows" {
|
||
ldflags = append(ldflags, "-H", "windowsgui") // inget konsolfönster
|
||
}
|
||
args = append(args, "-trimpath")
|
||
} else {
|
||
args = append(args, "-gcflags=all=-N -l") // ingen optimering/inlining -> bättre debugging
|
||
}
|
||
args = append(args, "-ldflags", strings.Join(ldflags, " "), mainPkg)
|
||
|
||
cmd := exec.Command("go", args...)
|
||
cmd.Dir = root
|
||
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||
cmd.Env = append(os.Environ(), "GOOS="+*goos, "GOARCH="+*goarch)
|
||
if *goos == "windows" {
|
||
// Ebitengine på Windows är ren Go -> korsbygge från Linux utan C-verktyg.
|
||
cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
|
||
}
|
||
start := time.Now()
|
||
fmt.Printf("» go %s\n", strings.Join(args, " "))
|
||
must(cmd.Run())
|
||
|
||
// Kopiera resources/ bredvid binären (ersätt gammal kopia).
|
||
dst := filepath.Join(outDir, "resources")
|
||
must(os.RemoveAll(dst))
|
||
n, err := copyTree(filepath.Join(root, "resources"), dst)
|
||
must(err)
|
||
|
||
st, _ := os.Stat(exePath)
|
||
fmt.Printf("✓ %s-bygge klart på %s\n binär: %s (%.1f MB, %s)\n resurser: %s (%d filer)\n",
|
||
*mode, time.Since(start).Round(time.Millisecond), rel(root, exePath), float64(st.Size())/1e6, version, rel(root, dst), n)
|
||
|
||
if *run {
|
||
if *goos != runtime.GOOS || *goarch != runtime.GOARCH {
|
||
fatal("kan inte köra en " + *goos + "/" + *goarch + "-binär här")
|
||
}
|
||
r := exec.Command(exePath, flag.Args()...)
|
||
r.Dir = outDir
|
||
r.Stdout, r.Stderr, r.Stdin = os.Stdout, os.Stderr, os.Stdin
|
||
if err := r.Run(); err != nil {
|
||
fatal(err.Error())
|
||
}
|
||
}
|
||
}
|
||
|
||
// repoRoot letar uppåt från arbetskatalogen efter go.mod.
|
||
func repoRoot() string {
|
||
dir, err := os.Getwd()
|
||
must(err)
|
||
for {
|
||
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
|
||
return dir
|
||
}
|
||
parent := filepath.Dir(dir)
|
||
if parent == dir {
|
||
fatal("hittade ingen go.mod ovanför arbetskatalogen – kör från repot")
|
||
}
|
||
dir = parent
|
||
}
|
||
}
|
||
|
||
func modulePath(root string) string {
|
||
b, err := os.ReadFile(filepath.Join(root, "go.mod"))
|
||
must(err)
|
||
for _, line := range strings.Split(string(b), "\n") {
|
||
if strings.HasPrefix(line, "module ") {
|
||
return strings.TrimSpace(strings.TrimPrefix(line, "module "))
|
||
}
|
||
}
|
||
fatal("go.mod saknar module-rad")
|
||
return ""
|
||
}
|
||
|
||
func gitVersion(root string) string {
|
||
cmd := exec.Command("git", "describe", "--tags", "--always", "--dirty")
|
||
cmd.Dir = root
|
||
out, err := cmd.Output()
|
||
if err != nil {
|
||
return "dev"
|
||
}
|
||
return strings.TrimSpace(string(out))
|
||
}
|
||
|
||
// copyTree kopierar en mapp rekursivt. README.md-filer i resursmappen är
|
||
// dokumentation för utvecklare och hoppas över.
|
||
func copyTree(src, dst string) (int, error) {
|
||
n := 0
|
||
err := filepath.WalkDir(src, func(p string, d os.DirEntry, err error) error {
|
||
if err != nil {
|
||
return err
|
||
}
|
||
relp, _ := filepath.Rel(src, p)
|
||
target := filepath.Join(dst, relp)
|
||
if d.IsDir() {
|
||
return os.MkdirAll(target, 0o755)
|
||
}
|
||
if strings.EqualFold(d.Name(), "README.md") {
|
||
return nil
|
||
}
|
||
in, err := os.Open(p)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer in.Close()
|
||
out, err := os.Create(target)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer out.Close()
|
||
if _, err := io.Copy(out, in); err != nil {
|
||
return err
|
||
}
|
||
n++
|
||
return nil
|
||
})
|
||
return n, err
|
||
}
|
||
|
||
func rel(root, p string) string {
|
||
if r, err := filepath.Rel(root, p); err == nil {
|
||
return r
|
||
}
|
||
return p
|
||
}
|
||
|
||
func must(err error) {
|
||
if err != nil {
|
||
fatal(err.Error())
|
||
}
|
||
}
|
||
|
||
func fatal(msg string) {
|
||
fmt.Fprintln(os.Stderr, "build: "+msg)
|
||
os.Exit(1)
|
||
}
|