/* 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 the Free Software Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Jobserver package main 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 init() { cmdName := CmdName() if !(cmdName == CmdNameRedo || cmdName == CmdNameRedoIfchange) { 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 switch { case *flagJobs == 0: jobs = 0 case *flagJobs > 0: jobs = uint64(*flagJobs) case jobsEnv != "": jobs, err = strconv.ParseUint(jobsEnv, 10, 64) if err != nil { log.Fatalln("can not parse", EnvJobs, err) } } if jobs == 0 { // infinite jobs return } JSR, JSW, err = os.Pipe() if err != nil { log.Fatalln(err) } tracef(CJS, "initial fill with %d", jobs) jsTokens[BMakeGoodToken] = int(jobs) for ; jobs > 0; jobs-- { jsReleaseNoLock(BMakeGoodToken) } } 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 } tracef(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(ctx string) byte { if JSR == nil { return BMakeGoodToken } tracef(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() tracef(CJS, "acquired for %s", ctx) return token[0] }