2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2021 Sergey Matveev <stargrave@stargrave.org>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
35 EnvMakeFlags = "MAKEFLAGS"
37 EnvJSFd = "REDO_JS_FD"
39 EnvJSToken = "REDO_JS_TOKEN"
43 MakeTypeBmake = "bmake"
44 MakeTypeGmake = "gmake"
48 // bmake (NetBSD make)
49 BMakeGoodToken = byte('+')
50 BMakeJSArg = "-j 1 -J "
51 BMakeJSArgRe = regexp.MustCompile(`(.*)\s*-J (\d+),(\d+)\s*(.*)`)
54 GMakeJSArg = "--jobserver-auth="
55 GMakeJSArgRe = regexp.MustCompile(`(.*)\s*--jobserver-auth=(\d+),(\d+)\s*(.*)`)
59 DMakeJSArgRe = regexp.MustCompile(`(.*)\s*(\d+),(\d+)\s*(.*)`)
61 MakeFlagsName = EnvMakeFlags
68 jsToken byte // got via EnvJSToken
72 flagJobs = flag.Int("j", -1, fmt.Sprintf("number of parallel jobs (0=inf, <0=1) (%s)", EnvJobs))
75 func jsStart(jobsEnv string) {
80 } else if *flagJobs > 0 {
81 jobs = uint64(*flagJobs)
82 } else if jobsEnv != "" {
83 jobs, err = strconv.ParseUint(jobsEnv, 10, 64)
85 log.Fatalln("can not parse", EnvJobs, err)
92 JSR, JSW, err = os.Pipe()
96 trace(CJS, "initial fill with %d", jobs)
97 jsTokens[BMakeGoodToken] = int(jobs)
98 for ; jobs > 0; jobs-- {
99 jsReleaseNoLock(BMakeGoodToken)
104 jsTokens = make(map[byte]int)
106 makeType := os.Getenv(EnvMake)
107 var makeArgRe *regexp.Regexp
110 makeArgRe = GMakeJSArgRe
111 MakeJSArg = GMakeJSArg
113 makeArgRe = BMakeJSArgRe
114 MakeJSArg = BMakeJSArg
118 MakeFlagsName = EnvJSFd
119 makeArgRe = DMakeJSArgRe
120 MakeJSArg = DMakeJSArg
122 log.Fatalln("unknown", EnvMake, "type")
125 MakeFlags = os.Getenv(MakeFlagsName)
126 jobsEnv := os.Getenv(EnvJobs)
128 // jobserver disabled, infinite jobs
132 // we are not running under make
137 match := makeArgRe.FindStringSubmatch(MakeFlags)
139 // MAKEFLAGS does not contain anything related to jobserver
143 MakeFlags = match[1] + " " + match[4]
147 if err := recover(); err != nil {
151 JSR = mustParseFd(match[2], "JSR")
152 JSW = mustParseFd(match[3], "JSW")
155 if token := os.Getenv(EnvJSToken); token != "" {
156 jsTokenInt, err := strconv.ParseUint(token, 10, 8)
158 log.Fatalln("invalid", EnvJSToken, "format:", err)
160 jsToken = byte(jsTokenInt)
162 jsRelease("ifchange entered", jsToken)
165 killed := make(chan os.Signal, 0)
166 signal.Notify(killed, syscall.SIGTERM, syscall.SIGINT)
170 for token, i := range jsTokens {
172 jsReleaseNoLock(token)
180 func jsReleaseNoLock(token byte) {
181 if n, err := JSW.Write([]byte{token}); err != nil || n != 1 {
182 log.Fatalln("can not write JSW:", err)
186 func jsRelease(ctx string, token byte) {
190 trace(CJS, "release from %s", ctx)
193 jsReleaseNoLock(token)
197 func jsAcquire(ctx string) byte {
199 return BMakeGoodToken
201 trace(CJS, "acquire for %s", ctx)
203 if n, err := JSR.Read(token); err != nil || n != 1 {
204 log.Fatalln("can not read JSR:", err)
209 trace(CJS, "acquired for %s", ctx)