]> Cypherpunks.ru repositories - goredo.git/blob - js.go
Unify dep*Read/Write name
[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         switch {
86         case *flagJobs == 0:
87                 jobs = 0
88         case *flagJobs > 0:
89                 jobs = uint64(*flagJobs)
90         case jobsEnv != "":
91                 jobs, err = strconv.ParseUint(jobsEnv, 10, 64)
92                 if err != nil {
93                         log.Fatalln("can not parse", EnvJobs, err)
94                 }
95         }
96         if jobs == 0 {
97                 // infinite jobs
98                 return
99         }
100         JSR, JSW, err = os.Pipe()
101         if err != nil {
102                 log.Fatalln(err)
103         }
104         tracef(CJS, "initial fill with %d", jobs)
105         jsTokens[BMakeGoodToken] = int(jobs)
106         for ; jobs > 0; jobs-- {
107                 jsReleaseNoLock(BMakeGoodToken)
108         }
109 }
110
111 func jsInit() {
112         jsTokens = make(map[byte]int)
113
114         makeType := os.Getenv(EnvMake)
115         var makeArgRe *regexp.Regexp
116         switch makeType {
117         case MakeTypeGmake:
118                 makeArgRe = GMakeJSArgRe
119                 MakeJSArg = GMakeJSArg
120         case MakeTypeBmake:
121                 makeArgRe = BMakeJSArgRe
122                 MakeJSArg = BMakeJSArg
123         case "":
124                 fallthrough
125         case MakeTypeNone:
126                 MakeFlagsName = EnvJSFd
127                 makeArgRe = DMakeJSArgRe
128                 MakeJSArg = DMakeJSArg
129         default:
130                 log.Fatalln("unknown", EnvMake, "type")
131         }
132
133         MakeFlags = os.Getenv(MakeFlagsName)
134         jobsEnv := os.Getenv(EnvJobs)
135         if jobsEnv == "NO" {
136                 // jobserver disabled, infinite jobs
137                 return
138         }
139         if MakeFlags == "" {
140                 // we are not running under make
141                 jsStart(jobsEnv)
142                 return
143         }
144
145         match := makeArgRe.FindStringSubmatch(MakeFlags)
146         if len(match) == 0 {
147                 // MAKEFLAGS does not contain anything related to jobserver
148                 jsStart(jobsEnv)
149                 return
150         }
151         MakeFlags = match[1] + " " + match[4]
152
153         func() {
154                 defer func() {
155                         if err := recover(); err != nil {
156                                 log.Fatalln(err)
157                         }
158                 }()
159                 JSR = mustParseFd(match[2], "JSR")
160                 JSW = mustParseFd(match[3], "JSW")
161         }()
162
163         if token := os.Getenv(EnvJSToken); token != "" {
164                 jsTokenInt, err := strconv.ParseUint(token, 10, 8)
165                 if err != nil {
166                         log.Fatalln("invalid", EnvJSToken, "format:", err)
167                 }
168                 jsToken = byte(jsTokenInt)
169                 jsTokens[jsToken]++
170                 jsRelease("ifchange entered", jsToken)
171         }
172 }
173
174 func jsReleaseNoLock(token byte) {
175         if n, err := JSW.Write([]byte{token}); err != nil || n != 1 {
176                 log.Fatalln("can not write JSW:", err)
177         }
178 }
179
180 func jsRelease(ctx string, token byte) {
181         if JSW == nil {
182                 return
183         }
184         tracef(CJS, "release from %s", ctx)
185         jsTokensM.Lock()
186         jsTokens[token]--
187         jsReleaseNoLock(token)
188         jsTokensM.Unlock()
189 }
190
191 func jsReleaseAll() {
192         jsTokensM.Lock()
193         for token, i := range jsTokens {
194                 for ; i > 0; i-- {
195                         jsReleaseNoLock(token)
196                 }
197         }
198         jsTokensM.Unlock()
199 }
200
201 func jsAcquire(ctx string) byte {
202         if JSR == nil {
203                 return BMakeGoodToken
204         }
205         tracef(CJS, "acquire for %s", ctx)
206         token := []byte{0}
207         if n, err := JSR.Read(token); err != nil || n != 1 {
208                 log.Fatalln("can not read JSR:", err)
209         }
210         jsTokensM.Lock()
211         jsTokens[token[0]]++
212         jsTokensM.Unlock()
213         tracef(CJS, "acquired for %s", ctx)
214         return token[0]
215 }