Files
gbld/gbld.go
T

454 lines
14 KiB
Go

// ============================================================================ simple golang builder (mwx'2026)
package main
import (
"bufio"
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
)
var version = "1.18.4"
var VERSIONURL = "http://gozilla.fhi.mpg.de/gbld/version.txt"
var UPDATEBASEURL = "http://gozilla.fhi.mpg.de/gbld"
var oses = []string{"darwin", "linux", "windows"}
var arches = []string{"arm64", "amd64"}
var GO string
var GOIMPORTS string
var done chan bool
func main() { // ========================================================================================== main
checkforupdate()
if len(os.Args) == 1 { // read predefined builds
c, err := os.ReadFile("gbld.menu.json")
if err == nil {
var sel []string
var buildjson map[string][]string
err := json.Unmarshal([]byte(c), &buildjson)
if err == nil {
for k := range buildjson {
sel = append(sel, k)
}
tmp := ""
err = survey.AskOne(&survey.Select{Message: "Select build", Options: sel}, &tmp)
os.Args = []string{"gbld"}
os.Args = append(os.Args, buildjson[tmp]...)
} else {
PE(err.Error())
os.Exit(0);
}
} else {
os.Args = []string{"gbld","-b","-i","-1",dirName()}
}
}
opt_b := flag.Bool("b", false, "")
opt_1 := flag.Bool("1", false, "")
opt_i := flag.Bool("i", false, "")
opt_x := flag.Bool("x", false, "")
opt_c := flag.String("c", "", "")
opt_o := flag.String("o", "", "")
opt_a := flag.Bool("a", false, "")
opt_v := flag.Bool("v", false, "")
opt_h := flag.Bool("h", false, "")
opt_u := flag.Bool("u", false, "")
flag.Usage = func() {
P()
info()
P()
P(Cw("Usage:"))
P(Cy(" gbld [-b] [-1] [-i] [-a] [-c <CMD>] [-o <OPTIONS>] [build name]"))
P(Cy(" gbld -u"))
P(Cw(" -b build only"))
P(Cw(" -1 run only once"))
P(Cw(" -i run gpimports before build"))
P(Cw(" -a build for all platforms"))
P(Cw(" -o <OPTIONS> exec options"))
P(Cw(" -c <CMD> run command after build"))
P(Cw(" -v show version"))
P(Cw(" -h show help"))
P(Cw(" -u upload builds to gozilla"))
P()
P(Cw(" · if build is supplied the current directory name will be used"))
P()
P(Cw(" · if no option supplied try to read '") + Cy("gbld.menu.json") + Cw("'"))
P(Cm(` {`))
P(Cm(` "build": ["-b","gbld"],`))
P(Cm(` "help": ["-h"]`))
P(Cm(` }`))
P()
P(Cw(" · if no option is supplied and no '") + Cy("gbld.menu.json") + Cw("' is available"))
P(Cw(" the current directory name with ") + Cy("-b -i -1") + Cw(" as options will be used"))
P()
}
flag.Parse()
path, err := exec.LookPath("go") // ··························································· check for 'go'
if err != nil {
fmt.Println(Crb("Error: ") + Cwb("go") + " not found")
os.Exit(0)
}
GO = path
path, err = exec.LookPath("goimports") // ·············································· check for 'goimports'
if err != nil {
fmt.Println(Crb("Error: ") + Cwb("goimports") + " not found")
os.Exit(0)
}
GOIMPORTS = path
if *opt_v { // ·················································································· show version
info()
os.Exit(0)
}
if *opt_h { // ····················································································· show help
flag.Usage()
os.Exit(0)
}
name := flag.Arg(0) // ································································ command line arguments
if len(name) == 0 {
name=dirName()
if len(name) == 0 {
flag.Usage()
os.Exit(0)
}
}
file := name + ".go"
if !fileExists(file) {
P(Crb("file not found: " + file))
os.Exit(0)
}
var ot int64 = 0
var st int = 1
var xmd *exec.Cmd = nil
if (!*opt_1) {
PN("\033[2J\033[1;1H")
info()
}
if *opt_u { // ······································································ upload builds to gozilla
targetversion:=targetversion(file)
for _, osys := range oses {
for _, arch := range arches {
ofile:="bin/"+name+"_"+targetversion+"_"+osys+"_"+arch
_, err := os.Stat(ofile)
if err == nil {
cmd := exec.Command("ssh","root@gozilla","rm", "/db/www/"+prgname()+"/*_"+osys+"_"+arch )
P(Cy(cmd))
cmd.Run()
cmd = exec.Command("scp",ofile, "root@gozilla:/db/www/"+prgname())
P(Cy(cmd))
cmd.Run()
}
}
}
_, err := os.Stat("bin/version.txt")
if err == nil {
cmd := exec.Command("scp","bin/version.txt", "root@gozilla:/db/www/"+prgname())
P(Cy(cmd))
cmd.Run()
}
os.Exit(0)
}
for { // ------------------------------------------------------------------------------------------- main loop
lmf := lmf("./")
var dt int64 = 0
if fileExists(name) {
dt = lmf - filemodtime(name)
} else {
dt = 1
}
if (dt > 0 && ot != lmf) || st == 1 { // ·············································· new version detected
if st == 0 && !*opt_1{
PN("\033[2J\033[1;1H")
}
P(Cc("--- start compiling " + file + " ---"))
stopRunning(xmd)
o := "" // ········································································· increase build number
readFile, err := os.Open("build.go")
if err == nil {
backup("build.go")
fileScanner := bufio.NewScanner(readFile)
fileScanner.Split(bufio.ScanLines)
r := regexp.MustCompile("var\\s+build\\s*=\\s*\"(.*)\"$")
for fileScanner.Scan() {
line := fileScanner.Text()
s := r.FindStringSubmatch(line)
if len(s) > 0 {
b := Atoi(s[1]) + 1
o = o + "var build = \"" + Itoa(b) + "\"\n"
P("build:", s[1], "->", b)
} else {
o = o + line + "\n"
}
}
readFile.Close()
os.WriteFile("build.go", []byte(o), 0644)
}
targetversion:=""
readFile, err = os.Open(file)
if err == nil {
fileScanner := bufio.NewScanner(readFile)
fileScanner.Split(bufio.ScanLines)
r := regexp.MustCompile("var\\s+version\\s*=\\s*\"(.*)\"$")
for fileScanner.Scan() {
line := fileScanner.Text()
s := r.FindStringSubmatch(line)
if len(s) > 0 {
targetversion=s[1]
}
}
readFile.Close()
}
if (targetversion=="") {
PE("target version not found")
os.Exit(0)
}
if *opt_i { // ············································································· run goimports
re := regexp.MustCompile(`(?s)import\s*\((.*?)\)`)
files, err := os.ReadDir(".")
if err != nil { fmt.Printf("Error: %v\n", err); return }
for _, f := range files {
if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") {
fileName := f.Name()
cmd := exec.Command("goimports", fileName)
output, err := cmd.Output()
if err != nil { continue }
origContent, _ := os.ReadFile(fileName)
tempMatch := re.FindStringSubmatch(string(output))
if len(tempMatch) < 2 { continue }
newImportBlock := tempMatch[1]
updatedContent := ReplaceFirst(re,string(origContent),fmt.Sprintf("import (%s)", newImportBlock))
if (string(origContent) != updatedContent) {
backup(fileName)
PF("%s %s\n",Cw("imports updated:"),Cmb(fileName))
os.WriteFile(fileName, []byte(updatedContent), 0644)
}
}
}
}
cmd := exec.Command("go", "build", "-o", "bin/"+name, name)
PF("%s %s\n", Cw("running:"), Cyb("go build "+name))
out, err := cmd.CombinedOutput()
if err != nil { // ·········································································· build failed
PN(Cy(string(out)))
P(Cr("--- build failed: " + name + " ---"))
} else { // ·············································································· build succeeded
if *opt_a { // ······························································· build for other platforms
for _, osys := range oses {
for _, arch := range arches {
ofile:="bin/"+name+"_"+targetversion+"_"+osys+"_"+arch
P(Cy(ofile))
cmd := exec.Command(GO, "build", "-o", ofile, name)
cmd.Env = append(os.Environ(), "GOOS="+osys, "GOARCH="+arch)
cmd.Run()
}
}
os.WriteFile("bin/version.txt", []byte(targetversion), 0644)
}
P(Cb("--- compiling done: " + name + " ---"))
if len(*opt_c) > 0 { // ··························································· execute post command
params := strings.Fields(*opt_c)
app := params[0]
args := params[1:]
P(Cw(*opt_c))
xmd = exec.Command(app, args...)
xmd.Stdout = os.Stdout
xmd.Stdin = os.Stdin
xmd.Stderr = os.Stderr
xmd.Run()
}
if !*opt_b && !*opt_a {
stopRunning(xmd)
args := []string{}
if len(*opt_o) > 0 {
args = strings.Fields(*opt_o)
}
xmd = exec.Command("./bin/"+name, args...) // ······································· start new build
var stdoutBuf, stderrBuf bytes.Buffer
xmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
xmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
xmd.Start()
PF("%s %d\n", Cw("process started:"), xmd.Process.Pid)
}
P(Cg("--- end building: " + name + " ---"))
if *opt_x {
os.Exit(0)
}
}
ot = lmf
}
if *opt_1 {
os.Exit(0)
}
st = 0
time.Sleep(500 * time.Millisecond)
}
}
func backup(src string) { // ---------------------------------------------------------------------- backup files
sourceFile,_ := os.Open(src)
defer sourceFile.Close()
dst:=SF("./tmp/%s.%s",src,time.Now().Format("20060102150405"))
destFile,_ := os.Create(dst)
defer destFile.Close()
io.Copy(destFile, sourceFile)
destFile.Sync()
destFile.Close()
}
// ===================================================================================================== SUPPORT
func targetversion(file string) string { // ---------------------------------------------- detect target version
targetversion:=""
readFile, err := os.Open(file)
if err == nil {
fileScanner := bufio.NewScanner(readFile)
fileScanner.Split(bufio.ScanLines)
r := regexp.MustCompile("var\\s+version\\s*=\\s*\"(.*)\"$")
for fileScanner.Scan() {
line := fileScanner.Text()
s := r.FindStringSubmatch(line)
if len(s) > 0 {
targetversion=s[1]
}
}
readFile.Close()
}
if (targetversion=="") {
PE("target version not found")
os.Exit(0)
}
return targetversion
}
func lmf(dir string) int64 { // -------------------------------------- get last modification date from .go files
var tmax int64 = 0
files, err := ioutil.ReadDir("./")
if err == nil {
r1, _ := regexp.Compile("^\\.")
r2, _ := regexp.Compile("build\\.go")
r3, _ := regexp.Compile("\\.go$")
for _, file := range files {
if r3.MatchString(file.Name()) {
if !r1.MatchString(file.Name()) && !r2.MatchString(file.Name()) {
tf := filemodtime(file.Name())
if tf > tmax {
tmax = tf
}
}
}
}
}
return tmax
}
func stopRunning(cmd *exec.Cmd) { // -------------------------------------------------- stop running old process
if cmd == nil || cmd.ProcessState != nil && cmd.ProcessState.Exited() || cmd.Process == nil {
return
}
err := cmd.Process.Kill()
if err == nil {
cmd.Wait()
P("process stopped:", cmd.Process.Pid)
}
}
func fileExists(filename string) bool { // ------------------------------------------------ check if file exists
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
func dirName() string { // ------------------------------------------------------- get name of current directory
pwd,err := os.Getwd()
if err == nil {
return filepath.Base(pwd)
}
return ""
}
func info() { // ------------------------------------------------------------------------------------- show info
PF("%s (%s (%s), toolbox %s, %s)\n", Cwb("gbld - simple golang builder"),
Cgb("v"+version), Cgb(build), Cgb("v"+tbversion), Ccb("mwx'2026"))
}
// ========================================================================================================= END