M0: skelett, byggkedja och dokumentation för JP Subway Pussel
- Go 1.27 + Ebitengine v2.10, intern upplösning 320x180 med heltalsskalning - internal/res (resursuppslag), internal/rng (seedad slump), internal/level (banformat, validering, BFS-lösbarhet) med tester; internal/archtest kontrollerar att bara presentationslagret beror på Ebitengine - tools/build: debug -> build/debug/, release -> build/release/<os>-<arch>/ med resources/ bredvid binären; Windows korsbyggs utan C-verktyg - VS Code tasks (build/test/release) och launch (dlv) - Första banan 001-flygplatsen, sv/en-texter, skyltordbok, placeholder-sprite - doc/: spelbeskrivning (renskriven), mål och tester, roadmap med teknikval (M0–M12), plan.md; README med bygg- och beroendeinformation Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
205
tools/build/main.go
Normal file
205
tools/build/main.go
Normal file
@@ -0,0 +1,205 @@
|
||||
// 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 = "jp-subway-pussel"
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user