]> Cypherpunks.ru repositories - goredo.git/blobdiff - js.go
default.do-s must not depend on themselves
[goredo.git] / js.go
diff --git a/js.go b/js.go
index d3ed7bb3fb752b5b7566c1251db7ae11b3c93a1f..1f2ec3985a463333781e738b291bcc00ab4d5e70 100644 (file)
--- a/js.go
+++ b/js.go
@@ -1,6 +1,6 @@
 /*
-goredo -- redo implementation on pure Go
-Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
+goredo -- djb's redo implementation on pure Go
+Copyright (C) 2020-2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -19,60 +19,196 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 package main
 
-import "os"
+import (
+       "flag"
+       "fmt"
+       "log"
+       "os"
+       "regexp"
+       "strconv"
+       "sync"
+)
+
+const (
+       EnvMakeFlags = "MAKEFLAGS"
+
+       EnvJSFd    = "REDO_JS_FD"
+       EnvJobs    = "REDO_JOBS"
+       EnvJSToken = "REDO_JS_TOKEN"
+       EnvMake    = "REDO_MAKE"
+
+       MakeTypeNone  = "none"
+       MakeTypeBmake = "bmake"
+       MakeTypeGmake = "gmake"
+)
 
 var (
+       // bmake (NetBSD make)
+       BMakeGoodToken = byte('+')
+       BMakeJSArg     = "-j 1 -J "
+       BMakeJSArgRe   = regexp.MustCompile(`(.*)\s*-J (\d+),(\d+)\s*(.*)`)
+
+       // GNU Make
+       GMakeJSArg   = "--jobserver-auth="
+       GMakeJSArgRe = regexp.MustCompile(`(.*)\s*--jobserver-auth=(\d+),(\d+)\s*(.*)`)
+
+       // dummy make
+       DMakeJSArg   = ""
+       DMakeJSArgRe = regexp.MustCompile(`(.*)\s*(\d+),(\d+)\s*(.*)`)
+
+       MakeFlagsName = EnvMakeFlags
+       MakeFlags     string
+       MakeJSArg     string
+
        JSR *os.File
        JSW *os.File
+
+       jsToken   byte // got via EnvJSToken
+       jsTokens  map[byte]int
+       jsTokensM sync.Mutex
+
+       flagJobs *int
 )
 
-func jsInit() {
-       jsrRaw := os.Getenv(RedoJSRFdEnv)
-       jswRaw := os.Getenv(RedoJSWFdEnv)
-       if (jsrRaw == "" && jswRaw != "") || (jsrRaw != "" && jswRaw == "") {
-               panic("both JSR and JSW must be set")
-       }
-       if jsrRaw == "NO" {
-               // infinite jobs
+func init() {
+       cmdName := CmdName()
+       if !(cmdName == CmdNameRedo || cmdName == CmdNameRedoIfchange) {
                return
        }
-       if jsrRaw != "" {
-               JSR = mustParseFd(jsrRaw, "JSR")
-               JSW = mustParseFd(jswRaw, "JSW")
-               jsRelease("ifchange entered")
-               return
+       flagJobs = flag.Int("j", -1,
+               fmt.Sprintf("number of parallel jobs (0=inf, <0=1) (%s)", EnvJobs))
+}
+
+func jsStart(jobsEnv string) {
+       jobs := uint64(1)
+       var err error
+       if *flagJobs == 0 {
+               jobs = 0
+       } else if *flagJobs > 0 {
+               jobs = uint64(*flagJobs)
+       } else if jobsEnv != "" {
+               jobs, err = strconv.ParseUint(jobsEnv, 10, 64)
+               if err != nil {
+                       log.Fatalln("can not parse", EnvJobs, err)
+               }
        }
-       if *JobsN == 0 {
+       if jobs == 0 {
                // infinite jobs
                return
        }
-       var err error
        JSR, JSW, err = os.Pipe()
        if err != nil {
-               panic(err)
+               log.Fatalln(err)
        }
-       for i := uint(0); i < *JobsN; i++ {
-               jsRelease("initial fill")
+       trace(CJS, "initial fill with %d", jobs)
+       jsTokens[BMakeGoodToken] = int(jobs)
+       for ; jobs > 0; jobs-- {
+               jsReleaseNoLock(BMakeGoodToken)
        }
 }
 
-func jsRelease(ctx string) {
+func jsInit() {
+       jsTokens = make(map[byte]int)
+
+       makeType := os.Getenv(EnvMake)
+       var makeArgRe *regexp.Regexp
+       switch makeType {
+       case MakeTypeGmake:
+               makeArgRe = GMakeJSArgRe
+               MakeJSArg = GMakeJSArg
+       case MakeTypeBmake:
+               makeArgRe = BMakeJSArgRe
+               MakeJSArg = BMakeJSArg
+       case "":
+               fallthrough
+       case MakeTypeNone:
+               MakeFlagsName = EnvJSFd
+               makeArgRe = DMakeJSArgRe
+               MakeJSArg = DMakeJSArg
+       default:
+               log.Fatalln("unknown", EnvMake, "type")
+       }
+
+       MakeFlags = os.Getenv(MakeFlagsName)
+       jobsEnv := os.Getenv(EnvJobs)
+       if jobsEnv == "NO" {
+               // jobserver disabled, infinite jobs
+               return
+       }
+       if MakeFlags == "" {
+               // we are not running under make
+               jsStart(jobsEnv)
+               return
+       }
+
+       match := makeArgRe.FindStringSubmatch(MakeFlags)
+       if len(match) == 0 {
+               // MAKEFLAGS does not contain anything related to jobserver
+               jsStart(jobsEnv)
+               return
+       }
+       MakeFlags = match[1] + " " + match[4]
+
+       func() {
+               defer func() {
+                       if err := recover(); err != nil {
+                               log.Fatalln(err)
+                       }
+               }()
+               JSR = mustParseFd(match[2], "JSR")
+               JSW = mustParseFd(match[3], "JSW")
+       }()
+
+       if token := os.Getenv(EnvJSToken); token != "" {
+               jsTokenInt, err := strconv.ParseUint(token, 10, 8)
+               if err != nil {
+                       log.Fatalln("invalid", EnvJSToken, "format:", err)
+               }
+               jsToken = byte(jsTokenInt)
+               jsTokens[jsToken]++
+               jsRelease("ifchange entered", jsToken)
+       }
+}
+
+func jsReleaseNoLock(token byte) {
+       if n, err := JSW.Write([]byte{token}); err != nil || n != 1 {
+               log.Fatalln("can not write JSW:", err)
+       }
+}
+
+func jsRelease(ctx string, token byte) {
        if JSW == nil {
                return
        }
        trace(CJS, "release from %s", ctx)
-       if n, err := JSW.Write([]byte{0}); err != nil || n != 1 {
-               panic("can not write JSW")
+       jsTokensM.Lock()
+       jsTokens[token]--
+       jsReleaseNoLock(token)
+       jsTokensM.Unlock()
+}
+
+func jsReleaseAll() {
+       jsTokensM.Lock()
+       for token, i := range jsTokens {
+               for ; i > 0; i-- {
+                       jsReleaseNoLock(token)
+               }
        }
+       jsTokensM.Unlock()
 }
 
-func jsAcquire(ctx string) {
+func jsAcquire(ctx string) byte {
        if JSR == nil {
-               return
+               return BMakeGoodToken
        }
        trace(CJS, "acquire for %s", ctx)
-       if n, err := JSR.Read([]byte{0}); err != nil || n != 1 {
-               panic("can not read JSR")
+       token := []byte{0}
+       if n, err := JSR.Read(token); err != nil || n != 1 {
+               log.Fatalln("can not read JSR:", err)
        }
+       jsTokensM.Lock()
+       jsTokens[token[0]]++
+       jsTokensM.Unlock()
        trace(CJS, "acquired for %s", ctx)
+       return token[0]
 }