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
77 if !(cmdName == CmdNameRedo || cmdName == CmdNameRedoIfchange) {
80 flagJobs = flag.Int("j", -1,
81 fmt.Sprintf("number of parallel jobs (0=inf, <0=1) (%s)", EnvJobs))
84 func jsStart(jobsEnv string) {
89 } else if *flagJobs > 0 {
90 jobs = uint64(*flagJobs)
91 } else if jobsEnv != "" {
92 jobs, err = strconv.ParseUint(jobsEnv, 10, 64)
94 log.Fatalln("can not parse", EnvJobs, err)
101 JSR, JSW, err = os.Pipe()
105 trace(CJS, "initial fill with %d", jobs)
106 jsTokens[BMakeGoodToken] = int(jobs)
107 for ; jobs > 0; jobs-- {
108 jsReleaseNoLock(BMakeGoodToken)
113 jsTokens = make(map[byte]int)
115 makeType := os.Getenv(EnvMake)
116 var makeArgRe *regexp.Regexp
119 makeArgRe = GMakeJSArgRe
120 MakeJSArg = GMakeJSArg
122 makeArgRe = BMakeJSArgRe
123 MakeJSArg = BMakeJSArg
127 MakeFlagsName = EnvJSFd
128 makeArgRe = DMakeJSArgRe
129 MakeJSArg = DMakeJSArg
131 log.Fatalln("unknown", EnvMake, "type")
134 MakeFlags = os.Getenv(MakeFlagsName)
135 jobsEnv := os.Getenv(EnvJobs)
137 // jobserver disabled, infinite jobs
141 // we are not running under make
146 match := makeArgRe.FindStringSubmatch(MakeFlags)
148 // MAKEFLAGS does not contain anything related to jobserver
152 MakeFlags = match[1] + " " + match[4]
156 if err := recover(); err != nil {
160 JSR = mustParseFd(match[2], "JSR")
161 JSW = mustParseFd(match[3], "JSW")
164 if token := os.Getenv(EnvJSToken); token != "" {
165 jsTokenInt, err := strconv.ParseUint(token, 10, 8)
167 log.Fatalln("invalid", EnvJSToken, "format:", err)
169 jsToken = byte(jsTokenInt)
171 jsRelease("ifchange entered", jsToken)
174 killed := make(chan os.Signal, 0)
175 signal.Notify(killed, syscall.SIGTERM, syscall.SIGINT)
179 for token, i := range jsTokens {
181 jsReleaseNoLock(token)
189 func jsReleaseNoLock(token byte) {
190 if n, err := JSW.Write([]byte{token}); err != nil || n != 1 {
191 log.Fatalln("can not write JSW:", err)
195 func jsRelease(ctx string, token byte) {
199 trace(CJS, "release from %s", ctx)
202 jsReleaseNoLock(token)
206 func jsAcquire(ctx string) byte {
208 return BMakeGoodToken
210 trace(CJS, "acquire for %s", ctx)
212 if n, err := JSR.Read(token); err != nil || n != 1 {
213 log.Fatalln("can not read JSR:", err)
218 trace(CJS, "acquired for %s", ctx)