]> Cypherpunks.ru repositories - goredo.git/blob - js.go
1f2ec3985a463333781e738b291bcc00ab4d5e70
[goredo.git] / js.go
1 /*
2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2021 Sergey Matveev <stargrave@stargrave.org>
4
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.
8
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.
13
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/>.
16 */
17
18 // Jobserver
19
20 package main
21
22 import (
23         "flag"
24         "fmt"
25         "log"
26         "os"
27         "regexp"
28         "strconv"
29         "sync"
30 )
31
32 const (
33         EnvMakeFlags = "MAKEFLAGS"
34
35         EnvJSFd    = "REDO_JS_FD"
36         EnvJobs    = "REDO_JOBS"
37         EnvJSToken = "REDO_JS_TOKEN"
38         EnvMake    = "REDO_MAKE"
39
40         MakeTypeNone  = "none"
41         MakeTypeBmake = "bmake"
42         MakeTypeGmake = "gmake"
43 )
44
45 var (
46         // bmake (NetBSD make)
47         BMakeGoodToken = byte('+')
48         BMakeJSArg     = "-j 1 -J "
49         BMakeJSArgRe   = regexp.MustCompile(`(.*)\s*-J (\d+),(\d+)\s*(.*)`)
50
51         // GNU Make
52         GMakeJSArg   = "--jobserver-auth="
53         GMakeJSArgRe = regexp.MustCompile(`(.*)\s*--jobserver-auth=(\d+),(\d+)\s*(.*)`)
54
55         // dummy make
56         DMakeJSArg   = ""
57         DMakeJSArgRe = regexp.MustCompile(`(.*)\s*(\d+),(\d+)\s*(.*)`)
58
59         MakeFlagsName = EnvMakeFlags
60         MakeFlags     string
61         MakeJSArg     string
62
63         JSR *os.File
64         JSW *os.File
65
66         jsToken   byte // got via EnvJSToken
67         jsTokens  map[byte]int
68         jsTokensM sync.Mutex
69
70         flagJobs *int
71 )
72
73 func init() {
74         cmdName := CmdName()
75         if !(cmdName == CmdNameRedo || cmdName == CmdNameRedoIfchange) {
76                 return
77         }
78         flagJobs = flag.Int("j", -1,
79                 fmt.Sprintf("number of parallel jobs (0=inf, <0=1) (%s)", EnvJobs))
80 }
81
82 func jsStart(jobsEnv string) {
83         jobs := uint64(1)
84         var err error
85         if *flagJobs == 0 {
86                 jobs = 0
87         } else if *flagJobs > 0 {
88                 jobs = uint64(*flagJobs)
89         } else if jobsEnv != "" {
90                 jobs, err = strconv.ParseUint(jobsEnv, 10, 64)
91                 if err != nil {
92                         log.Fatalln("can not parse", EnvJobs, err)
93                 }
94         }
95         if jobs == 0 {
96                 // infinite jobs
97                 return
98         }
99         JSR, JSW, err = os.Pipe()
100         if err != nil {
101                 log.Fatalln(err)
102         }
103         trace(CJS, "initial fill with %d", jobs)
104         jsTokens[BMakeGoodToken] = int(jobs)
105         for ; jobs > 0; jobs-- {
106                 jsReleaseNoLock(BMakeGoodToken)
107         }
108 }
109
110 func jsInit() {
111         jsTokens = make(map[byte]int)
112
113         makeType := os.Getenv(EnvMake)
114         var makeArgRe *regexp.Regexp
115         switch makeType {
116         case MakeTypeGmake:
117                 makeArgRe = GMakeJSArgRe
118                 MakeJSArg = GMakeJSArg
119         case MakeTypeBmake:
120                 makeArgRe = BMakeJSArgRe
121                 MakeJSArg = BMakeJSArg
122         case "":
123                 fallthrough
124         case MakeTypeNone:
125                 MakeFlagsName = EnvJSFd
126                 makeArgRe = DMakeJSArgRe
127                 MakeJSArg = DMakeJSArg
128         default:
129                 log.Fatalln("unknown", EnvMake, "type")
130         }
131
132         MakeFlags = os.Getenv(MakeFlagsName)
133         jobsEnv := os.Getenv(EnvJobs)
134         if jobsEnv == "NO" {
135                 // jobserver disabled, infinite jobs
136                 return
137         }
138         if MakeFlags == "" {
139                 // we are not running under make
140                 jsStart(jobsEnv)
141                 return
142         }
143
144         match := makeArgRe.FindStringSubmatch(MakeFlags)
145         if len(match) == 0 {
146                 // MAKEFLAGS does not contain anything related to jobserver
147                 jsStart(jobsEnv)
148                 return
149         }
150         MakeFlags = match[1] + " " + match[4]
151
152         func() {
153                 defer func() {
154                         if err := recover(); err != nil {
155                                 log.Fatalln(err)
156                         }
157                 }()
158                 JSR = mustParseFd(match[2], "JSR")
159                 JSW = mustParseFd(match[3], "JSW")
160         }()
161
162         if token := os.Getenv(EnvJSToken); token != "" {
163                 jsTokenInt, err := strconv.ParseUint(token, 10, 8)
164                 if err != nil {
165                         log.Fatalln("invalid", EnvJSToken, "format:", err)
166                 }
167                 jsToken = byte(jsTokenInt)
168                 jsTokens[jsToken]++
169                 jsRelease("ifchange entered", jsToken)
170         }
171 }
172
173 func jsReleaseNoLock(token byte) {
174         if n, err := JSW.Write([]byte{token}); err != nil || n != 1 {
175                 log.Fatalln("can not write JSW:", err)
176         }
177 }
178
179 func jsRelease(ctx string, token byte) {
180         if JSW == nil {
181                 return
182         }
183         trace(CJS, "release from %s", ctx)
184         jsTokensM.Lock()
185         jsTokens[token]--
186         jsReleaseNoLock(token)
187         jsTokensM.Unlock()
188 }
189
190 func jsReleaseAll() {
191         jsTokensM.Lock()
192         for token, i := range jsTokens {
193                 for ; i > 0; i-- {
194                         jsReleaseNoLock(token)
195                 }
196         }
197         jsTokensM.Unlock()
198 }
199
200 func jsAcquire(ctx string) byte {
201         if JSR == nil {
202                 return BMakeGoodToken
203         }
204         trace(CJS, "acquire for %s", ctx)
205         token := []byte{0}
206         if n, err := JSR.Read(token); err != nil || n != 1 {
207                 log.Fatalln("can not read JSR:", err)
208         }
209         jsTokensM.Lock()
210         jsTokens[token[0]]++
211         jsTokensM.Unlock()
212         trace(CJS, "acquired for %s", ctx)
213         return token[0]
214 }