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