Refactor watcher to use cross-platform fsnotify, remove deprecated platform-specific files, and add comprehensive README.md
This commit is contained in:
@@ -0,0 +1,89 @@
|
|||||||
|
# gbld — Simple Go Builder & Live-Reloader
|
||||||
|
|
||||||
|
`gbld` is a hot-reloader and build-automation utility for Go projects. It automatically tracks build numbers, formats source files using `goimports`, compiles the binary, manages the running process, and watches the project directory for changes to trigger automatic rebuilding and restarting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
- 🔄 **Hot Reloader**: Automatically watches for file changes (via `fsnotify`) and rebuilds/restarts your application instantly.
|
||||||
|
- 🔢 **Build Counter**: Increments a build number in `build.go` automatically with every compiled build.
|
||||||
|
- 🧹 **Auto-Import & Formatting**: Runs `goimports` on save (optional via `-i`) to keep your imports and formatting clean.
|
||||||
|
- 🖥️ **Interactive Menu**: Uses a select menu (`gbld.menu.json`) if run without arguments, letting you define and run custom build tasks.
|
||||||
|
- 🚀 **Cross-Compilation**: Easily builds binaries for multiple target operating systems (macOS, Linux, Windows) and architectures (amd64, arm64) using a single command.
|
||||||
|
- ☁️ **Remote Uploads**: Integrated command to securely upload build artifacts to a deployment server (`gozilla`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
Make sure you have [Go](https://go.dev/) and `goimports` installed and available in your system path:
|
||||||
|
```bash
|
||||||
|
go install golang.org/x/tools/cmd/goimports@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building from Source
|
||||||
|
Clone the repository and compile `gbld`:
|
||||||
|
```bash
|
||||||
|
git clone https://git.fhi.mpg.de/mike/gbld.git
|
||||||
|
cd gbld
|
||||||
|
go build -o /usr/local/bin/gbld
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gbld [flags] [target_name]
|
||||||
|
```
|
||||||
|
> [!NOTE]
|
||||||
|
> If `target_name` is omitted, the name of the current directory is used. The tool expects a main source file matching that name (e.g., `target_name.go`).
|
||||||
|
|
||||||
|
### CLI Flags
|
||||||
|
|
||||||
|
| Flag | Argument | Description |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `-b` | None | **Build-only** mode (compiles the binary but does not run it). |
|
||||||
|
| `-1` | None | Run **once** (does not watch the directory for changes). |
|
||||||
|
| `-i` | None | Runs `goimports` to clean up imports before building. |
|
||||||
|
| `-o` | `<args>` | Space-separated arguments to pass to the compiled binary on execution. |
|
||||||
|
| `-c` | `<command>` | Command to execute immediately after a successful build (before execution). |
|
||||||
|
| `-a` | None | Builds binaries for all supported platforms (Windows, macOS, Linux for AMD64/ARM64). |
|
||||||
|
| `-u` | None | Uploads built binaries via SSH/SCP to the `gozilla` server. |
|
||||||
|
| `-v` | None | Displays the current version of `gbld`. |
|
||||||
|
| `-h` | None | Displays help and usage information. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration (`gbld.menu.json`)
|
||||||
|
|
||||||
|
To make running common tasks easier, you can place a `gbld.menu.json` file in your project directory. If you run `gbld` without any flags, it will parse this file and present an interactive selection menu:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Build and Run (hot reload)": ["-i"],
|
||||||
|
"Build Only": ["-b", "-i", "-1"],
|
||||||
|
"Show Help": ["-h"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Under the Hood
|
||||||
|
|
||||||
|
### Build Increment
|
||||||
|
When compiling, `gbld` parses `build.go` to find a pattern matching `var build = "<number>"`. It increments this number by 1 and updates the file automatically. This is useful for stamping build numbers/versions inside your binary.
|
||||||
|
|
||||||
|
### Automatic Backups
|
||||||
|
Before `gbld` runs `goimports` on a file or modifies `build.go`, it creates a timestamped backup in a `./tmp/` directory inside the project workspace:
|
||||||
|
```
|
||||||
|
./tmp/gbld.go.20260625145800
|
||||||
|
```
|
||||||
|
|
||||||
|
### Process Management
|
||||||
|
When hot-reloading, `gbld` handles process termination gracefully:
|
||||||
|
1. It sends an `os.Interrupt` (SIGINT) to the running binary.
|
||||||
|
2. It waits up to 1 second for the process to exit clean.
|
||||||
|
3. If it does not exit within the timeout, it terminates the process forcefully (`SIGKILL`).
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
var build = "354"
|
var build = "362"
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.19.6"
|
var version = "1.20.1"
|
||||||
|
|
||||||
var UPDATEURL = "http://gozilla.fhi.mpg.de/gbld"
|
var UPDATEURL = "http://gozilla.fhi.mpg.de/gbld"
|
||||||
|
|
||||||
@@ -27,9 +28,14 @@ var arches = []string{"arm64", "amd64"}
|
|||||||
|
|
||||||
var GO string
|
var GO string
|
||||||
var GOIMPORTS string
|
var GOIMPORTS string
|
||||||
|
var xmd *exec.Cmd
|
||||||
|
|
||||||
var done chan bool
|
var done chan bool
|
||||||
var r1, r2, r3 *regexp.Regexp
|
var r1 = regexp.MustCompile(`^\.`)
|
||||||
|
var r2 = regexp.MustCompile(`build\.go`)
|
||||||
|
var r3 = regexp.MustCompile(`\.go$`)
|
||||||
|
var r4 = regexp.MustCompile(`var\s+build\s*=\s*"(.*)"$`)
|
||||||
|
var r5 = regexp.MustCompile(`(?i)var\s+version\s*=\s*"(.*)"$`)
|
||||||
|
|
||||||
func main() { // ========================================================================================== main
|
func main() { // ========================================================================================== main
|
||||||
|
|
||||||
@@ -52,7 +58,7 @@ func main() { // ===============================================================
|
|||||||
os.Args = append(os.Args, buildjson[tmp]...)
|
os.Args = append(os.Args, buildjson[tmp]...)
|
||||||
} else {
|
} else {
|
||||||
PE(err.Error())
|
PE(err.Error())
|
||||||
os.Exit(0);
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
os.Args = []string{"gbld","-b","-i","-1",dirName()}
|
os.Args = []string{"gbld","-b","-i","-1",dirName()}
|
||||||
@@ -105,21 +111,18 @@ func main() { // ===============================================================
|
|||||||
path, err := exec.LookPath("go") // ··························································· check for 'go'
|
path, err := exec.LookPath("go") // ··························································· check for 'go'
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(Crb("Error: ") + Cwb("go") + " not found")
|
fmt.Println(Crb("Error: ") + Cwb("go") + " not found")
|
||||||
os.Exit(0)
|
os.Exit(1)
|
||||||
|
|
||||||
}
|
}
|
||||||
GO = path
|
GO = path
|
||||||
|
|
||||||
|
|
||||||
path, err = exec.LookPath("goimports") // ·············································· check for 'goimports'
|
path, err = exec.LookPath("goimports") // ·············································· check for 'goimports'
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(Crb("Error: ") + Cwb("goimports") + " not found")
|
fmt.Println(Crb("Error: ") + Cwb("goimports") + " not found")
|
||||||
os.Exit(0)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
GOIMPORTS = path
|
GOIMPORTS = path
|
||||||
|
|
||||||
|
if *opt_v { // ·············································································· show version
|
||||||
if *opt_v { // ·················································································· show version
|
|
||||||
info()
|
info()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
@@ -134,31 +137,26 @@ func main() { // ===============================================================
|
|||||||
name=dirName()
|
name=dirName()
|
||||||
if len(name) == 0 {
|
if len(name) == 0 {
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
os.Exit(0)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
file := name + ".go"
|
file := name + ".go"
|
||||||
if !fileExists(file) {
|
if !fileExists(file) {
|
||||||
P(Crb("file not found: " + file))
|
P(Crb("file not found: " + file))
|
||||||
os.Exit(0)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ot int64 = 0
|
|
||||||
var st int = 1
|
|
||||||
var xmd *exec.Cmd = nil
|
|
||||||
|
|
||||||
if (!*opt_1 && !*opt_a && !*opt_u) {
|
if (!*opt_1 && !*opt_a && !*opt_u) {
|
||||||
PN("\033[2J\033[1;1H")
|
PN("\033[2J\033[1;1H")
|
||||||
}
|
}
|
||||||
info()
|
info()
|
||||||
|
|
||||||
if *opt_a { // ······························································· build for other platforms
|
if *opt_a { // ······························································· build for other platforms
|
||||||
targetversion:=gettargetversion(file)
|
targetversion:=gettargetversion()
|
||||||
P(Ccb("build for all platforms"))
|
P(Ccb("build for all platforms"))
|
||||||
for _, osys := range oses {
|
for _, osys := range oses {
|
||||||
for _, arch := range arches {
|
for _, arch := range arches {
|
||||||
|
|
||||||
ofile:="bin/"+name+"_"+targetversion+"_"+osys+"_"+arch
|
ofile:="bin/"+name+"_"+targetversion+"_"+osys+"_"+arch
|
||||||
P(Cy(ofile))
|
P(Cy(ofile))
|
||||||
|
|
||||||
@@ -167,13 +165,11 @@ func main() { // ===============================================================
|
|||||||
cmd.Run()
|
cmd.Run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
os.WriteFile("bin/version.txt", []byte(targetversion), 0644)
|
os.WriteFile("bin/version.txt", []byte(targetversion), 0644)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if *opt_u { // ······································································ upload builds to gozilla
|
if *opt_u { // ······································································ upload builds to gozilla
|
||||||
targetversion:=gettargetversion(file)
|
targetversion:=gettargetversion()
|
||||||
P(Ccb("upload builds to gozilla"))
|
P(Ccb("upload builds to gozilla"))
|
||||||
|
|
||||||
for _, osys := range oses {
|
for _, osys := range oses {
|
||||||
@@ -181,12 +177,10 @@ func main() { // ===============================================================
|
|||||||
ofile:="bin/"+name+"_"+targetversion+"_"+osys+"_"+arch
|
ofile:="bin/"+name+"_"+targetversion+"_"+osys+"_"+arch
|
||||||
_, err := os.Stat(ofile)
|
_, err := os.Stat(ofile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
||||||
cmd := exec.Command("ssh","root@gozilla","rm", "/db/www/"+name+"/*_"+osys+"_"+arch )
|
cmd := exec.Command("ssh","root@gozilla","rm", "/db/www/"+name+"/*_"+osys+"_"+arch )
|
||||||
P(Cy(cmd))
|
P(Cy(cmd))
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
|
|
||||||
|
|
||||||
cmd = exec.Command("scp",ofile, "root@gozilla:/db/www/"+name)
|
cmd = exec.Command("scp",ofile, "root@gozilla:/db/www/"+name)
|
||||||
P(Cy(cmd))
|
P(Cy(cmd))
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
@@ -199,168 +193,171 @@ func main() { // ===============================================================
|
|||||||
P(Cy(cmd))
|
P(Cy(cmd))
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (*opt_a || *opt_u) { os.Exit(0) }
|
if (*opt_a || *opt_u) { os.Exit(0) }
|
||||||
|
|
||||||
r1 = regexp.MustCompile("^\\.")
|
|
||||||
r2 = regexp.MustCompile("build\\.go")
|
|
||||||
r3 = regexp.MustCompile("\\.go$")
|
|
||||||
|
|
||||||
for { // ------------------------------------------------------------------------------------------- main loop
|
// Initial build
|
||||||
lmf := lmf("./")
|
buildAndRun(name, file, *opt_b, *opt_1, *opt_i, *opt_c, *opt_o, *opt_x)
|
||||||
|
|
||||||
var dt int64 = 0
|
if *opt_1 {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
if fileExists(name) {
|
// Set up fsnotify watcher
|
||||||
dt = lmf - filemodtime(name)
|
watcher, err := fsnotify.NewWatcher()
|
||||||
} else {
|
if err != nil {
|
||||||
dt = 1
|
PE(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
err = watcher.Add(".")
|
||||||
|
if err != nil {
|
||||||
|
PE(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
events := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
|
||||||
|
fileName := filepath.Base(event.Name)
|
||||||
|
if r3.MatchString(fileName) && !r1.MatchString(fileName) && !r2.MatchString(fileName) {
|
||||||
|
events <- true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case err, ok := <-watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
PE(err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if (dt > 0 && ot != lmf) || st == 1 { // ·············································· new version detected
|
var timer *time.Timer
|
||||||
|
for {
|
||||||
if st == 0 && !*opt_1{
|
select {
|
||||||
PN("\033[2J\033[1;1H")
|
case <-events:
|
||||||
|
if timer != nil {
|
||||||
|
timer.Stop()
|
||||||
}
|
}
|
||||||
P(Cc("--- start compiling " + file + " ---"))
|
timer = time.AfterFunc(100*time.Millisecond, func() {
|
||||||
|
buildAndRun(name, file, *opt_b, *opt_1, *opt_i, *opt_c, *opt_o, *opt_x)
|
||||||
stopRunning(xmd)
|
})
|
||||||
|
|
||||||
o := "" // ········································································· increase build number
|
|
||||||
|
|
||||||
readFile, err := os.Open("build.go")
|
|
||||||
if err == nil {
|
|
||||||
defer readFile.Close()
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
|
||||||
|
|
||||||
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 buildAndRun(name, file string, opt_b, opt_1, opt_i bool, opt_c, opt_o string, opt_x bool) {
|
||||||
|
PN("\033[2J\033[1;1H")
|
||||||
|
P(Cc("--- start compiling " + file + " ---"))
|
||||||
|
|
||||||
|
stopRunning(xmd)
|
||||||
|
|
||||||
|
o := "" // ········································································· increase build number
|
||||||
|
func() {
|
||||||
|
readFile, err := os.Open("build.go")
|
||||||
|
if err == nil {
|
||||||
|
defer readFile.Close()
|
||||||
|
backup("build.go")
|
||||||
|
fileScanner := bufio.NewScanner(readFile)
|
||||||
|
fileScanner.Split(bufio.ScanLines)
|
||||||
|
for fileScanner.Scan() {
|
||||||
|
line := fileScanner.Text()
|
||||||
|
s := r4.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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.WriteFile("build.go", []byte(o), 0644)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_ = gettargetversion()
|
||||||
|
|
||||||
|
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
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func backup(src string) { // ---------------------------------------------------------------------- backup files
|
func backup(src string) { // ---------------------------------------------------------------------- backup files
|
||||||
sourceFile,_ := os.Open(src)
|
sourceFile,_ := os.Open(src)
|
||||||
@@ -373,61 +370,61 @@ func backup(src string) { // ---------------------------------------------------
|
|||||||
destFile.Close()
|
destFile.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================================================== SUPPORT
|
func gettargetversion() string { // ------------------------------------------- detect target version
|
||||||
|
files, err := filepath.Glob("*.go")
|
||||||
func gettargetversion(file string) string { // ------------------------------------------- detect target version
|
if err != nil {
|
||||||
targetversion:=""
|
PE(SF("Error listing files: %v", err))
|
||||||
readFile, err := os.Open(file)
|
os.Exit(1)
|
||||||
if err == nil {
|
}
|
||||||
|
|
||||||
|
targetversion := ""
|
||||||
|
for _, file := range files {
|
||||||
|
readFile, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
fileScanner := bufio.NewScanner(readFile)
|
fileScanner := bufio.NewScanner(readFile)
|
||||||
fileScanner.Split(bufio.ScanLines)
|
fileScanner.Split(bufio.ScanLines)
|
||||||
r := regexp.MustCompile("var\\s+version\\s*=\\s*\"(.*)\"$")
|
|
||||||
for fileScanner.Scan() {
|
for fileScanner.Scan() {
|
||||||
line := fileScanner.Text()
|
line := fileScanner.Text()
|
||||||
s := r.FindStringSubmatch(line)
|
s := r5.FindStringSubmatch(line)
|
||||||
if len(s) > 0 {
|
if len(s) > 0 {
|
||||||
targetversion=s[1]
|
targetversion = s[1]
|
||||||
|
readFile.Close()
|
||||||
|
return targetversion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
readFile.Close()
|
readFile.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetversion=="") {
|
if targetversion == "" {
|
||||||
PE("target version not found")
|
PE("target version not found")
|
||||||
os.Exit(0)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetversion
|
return targetversion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func lmf(dir string) int64 { // -------------------------------------- get last modification date from .go files
|
|
||||||
var tmax int64 = 0
|
|
||||||
files, err := os.ReadDir(dir)
|
|
||||||
if err == nil {
|
|
||||||
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
|
func stopRunning(cmd *exec.Cmd) { // -------------------------------------------------- stop running old process
|
||||||
if cmd == nil || cmd.ProcessState != nil && cmd.ProcessState.Exited() || cmd.Process == nil {
|
if cmd == nil || cmd.ProcessState != nil && cmd.ProcessState.Exited() || cmd.Process == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := cmd.Process.Kill()
|
err := cmd.Process.Signal(os.Interrupt)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
cmd.Wait()
|
done := make(chan error, 1)
|
||||||
|
go func() { done <- cmd.Wait() }()
|
||||||
|
select {
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
cmd.Process.Kill()
|
||||||
|
<-done
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
P("process stopped:", cmd.Process.Pid)
|
P("process stopped:", cmd.Process.Pid)
|
||||||
|
} else {
|
||||||
|
cmd.Process.Kill()
|
||||||
|
cmd.Wait()
|
||||||
|
P("process stopped forcefully:", cmd.Process.Pid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ go 1.26.1
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
|
github.com/Masterminds/semver/v3 v3.5.0
|
||||||
github.com/eknkc/basex v1.0.1
|
github.com/eknkc/basex v1.0.1
|
||||||
github.com/fatih/color v1.19.0
|
github.com/fatih/color v1.19.0
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0
|
||||||
github.com/minio/selfupdate v0.6.0
|
github.com/minio/selfupdate v0.6.0
|
||||||
github.com/spf13/viper v1.21.0
|
github.com/spf13/viper v1.21.0
|
||||||
github.com/tidwall/gjson v1.19.0
|
github.com/tidwall/gjson v1.19.0
|
||||||
@@ -13,8 +15,6 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
aead.dev/minisign v0.2.0 // indirect
|
aead.dev/minisign v0.2.0 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.5.0 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func filemodtime(build string) int64 {
|
|
||||||
fibuild, _ := os.Stat(build)
|
|
||||||
tb := fibuild.Sys().(*syscall.Stat_t).Mtim
|
|
||||||
return tb.Sec
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user