1 // goredo -- djb's redo implementation on pure Go
2 // Copyright (C) 2020-2024 Sergey Matveev <stargrave@stargrave.org>
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
31 EnvMakeFlags = "MAKEFLAGS"
33 EnvJSFd = "REDO_JS_FD"
35 EnvJSToken = "REDO_JS_TOKEN"
39 MakeTypeBmake = "bmake"
40 MakeTypeGmake = "gmake"
44 // bmake (NetBSD make)
45 BMakeGoodToken = byte('+')
46 BMakeJSArg = "-j 1 -J "
47 BMakeJSArgRe = regexp.MustCompile(`(.*)\s*-J (\d+),(\d+)\s*(.*)`)
50 GMakeJSArg = "--jobserver-auth="
51 GMakeJSArgRe = regexp.MustCompile(`(.*)\s*--jobserver-auth=(\d+),(\d+)\s*(.*)`)
55 DMakeJSArgRe = regexp.MustCompile(`(.*)\s*(\d+),(\d+)\s*(.*)`)
57 MakeFlagsName = EnvMakeFlags
64 jsToken byte // got via EnvJSToken
73 if !(cmdName == CmdNameRedo || cmdName == CmdNameRedoIfchange) {
76 flagJobs = flag.Int("j", -1,
77 fmt.Sprintf("number of parallel jobs (0=inf, <0=1) (%s)", EnvJobs))
80 func jsStart(jobsEnv string) {
87 jobs = uint64(*flagJobs)
89 jobs, err = strconv.ParseUint(jobsEnv, 10, 64)
91 log.Fatalln("can not parse", EnvJobs, err)
98 JSR, JSW, err = os.Pipe()
102 tracef(CJS, "initial fill with %d", jobs)
103 jsTokens[BMakeGoodToken] = int(jobs)
104 for ; jobs > 0; jobs-- {
105 jsReleaseNoLock(BMakeGoodToken)
110 jsTokens = make(map[byte]int)
112 makeType := os.Getenv(EnvMake)
113 var makeArgRe *regexp.Regexp
116 makeArgRe = GMakeJSArgRe
117 MakeJSArg = GMakeJSArg
119 makeArgRe = BMakeJSArgRe
120 MakeJSArg = BMakeJSArg
124 MakeFlagsName = EnvJSFd
125 makeArgRe = DMakeJSArgRe
126 MakeJSArg = DMakeJSArg
128 log.Fatalln("unknown", EnvMake, "type")
131 MakeFlags = os.Getenv(MakeFlagsName)
132 jobsEnv := os.Getenv(EnvJobs)
134 // jobserver disabled, infinite jobs
138 // we are not running under make
143 match := makeArgRe.FindStringSubmatch(MakeFlags)
145 // MAKEFLAGS does not contain anything related to jobserver
149 MakeFlags = match[1] + " " + match[4]
153 if err := recover(); err != nil {
157 JSR = mustParseFd(match[2], "JSR")
158 JSW = mustParseFd(match[3], "JSW")
161 if token := os.Getenv(EnvJSToken); token != "" {
162 jsTokenInt, err := strconv.ParseUint(token, 10, 8)
164 log.Fatalln("invalid", EnvJSToken, "format:", err)
166 jsToken = byte(jsTokenInt)
168 jsRelease("ifchange entered", jsToken)
172 func jsReleaseNoLock(token byte) {
173 if n, err := JSW.Write([]byte{token}); err != nil || n != 1 {
174 log.Fatalln("can not write JSW:", err)
178 func jsRelease(ctx string, token byte) {
182 tracef(CJS, "release from %s", ctx)
185 jsReleaseNoLock(token)
189 func jsReleaseAll() {
191 for token, i := range jsTokens {
193 jsReleaseNoLock(token)
199 func jsAcquire(ctx string) byte {
201 return BMakeGoodToken
203 tracef(CJS, "acquire for %s", ctx)
205 if n, err := JSR.Read(token); err != nil || n != 1 {
206 log.Fatalln("can not read JSR:", err)
211 tracef(CJS, "acquired for %s", ctx)