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