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