X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=js.go;h=1f2ec3985a463333781e738b291bcc00ab4d5e70;hb=296f354f621049eff78bc039d1f2d347de416636;hp=ab1190e00c00759f48899030271986704d3c9ae4;hpb=f15fe27971bcf37a06bb9542ea1907f11444515a;p=goredo.git diff --git a/js.go b/js.go index ab1190e..1f2ec39 100644 --- a/js.go +++ b/js.go @@ -1,6 +1,6 @@ /* -goredo -- redo implementation on pure Go -Copyright (C) 2020 Sergey Matveev +goredo -- djb's redo implementation on pure Go +Copyright (C) 2020-2021 Sergey Matveev 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,59 +19,196 @@ along with this program. If not, see . 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() - 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() + trace(CJS, "initial fill with %d", jobs) + jsTokens[BMakeGoodToken] = int(jobs) + for ; jobs > 0; jobs-- { + jsReleaseNoLock(BMakeGoodToken) } } -func jsRelease() { +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") - if n, err := JSW.Write([]byte{0}); err != nil || n != 1 { - panic("can not write JSW") + trace(CJS, "release from %s", ctx) + 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() { +func jsAcquire(ctx string) byte { if JSR == nil { - return + return BMakeGoodToken } - trace(CJS, "acquire") - if n, err := JSR.Read([]byte{0}); err != nil || n != 1 { - panic("can not read JSR") + trace(CJS, "acquire for %s", ctx) + 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] }