]> Cypherpunks.ru repositories - goredo.git/blob - dep.go
4d57c917b3e5fe75f360653030e754b4d081ba77
[goredo.git] / dep.go
1 /*
2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2023 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 // Dependencies saver
19
20 package main
21
22 import (
23         "bufio"
24         "bytes"
25         "encoding/hex"
26         "errors"
27         "io"
28         "log"
29         "os"
30         "path"
31
32         "go.cypherpunks.ru/recfile"
33         "lukechampine.com/blake3"
34 )
35
36 var (
37         DirPrefix string
38         DepCwd    string
39
40         ErrBadRecFormat = errors.New("invalid format of .rec")
41         InodeCache      = make(map[string][]*Inode)
42         HashCache       = make(map[string][]Hash)
43 )
44
45 func recfileWrite(fdDep io.StringWriter, fields ...recfile.Field) error {
46         w := recfile.NewWriter(fdDep)
47         if _, err := w.RecordStart(); err != nil {
48                 return err
49         }
50         if _, err := w.WriteFields(fields...); err != nil {
51                 return err
52         }
53         return nil
54 }
55
56 func ifcreate(fdDep *os.File, tgt string) error {
57         tracef(CDebug, "ifcreate: %s <- %s", fdDep.Name(), tgt)
58         return recfileWrite(
59                 fdDep,
60                 recfile.Field{Name: "Type", Value: DepTypeIfcreate},
61                 recfile.Field{Name: "Target", Value: tgt},
62         )
63 }
64
65 func always(fdDep *os.File) error {
66         tracef(CDebug, "always: %s", fdDep.Name())
67         return recfileWrite(fdDep, recfile.Field{Name: "Type", Value: DepTypeAlways})
68 }
69
70 func stamp(fdDep, src *os.File) error {
71         hsh, err := fileHash(src)
72         if err != nil {
73                 return err
74         }
75         tracef(CDebug, "stamp: %s <- %s", fdDep.Name(), hsh)
76         return recfileWrite(
77                 fdDep,
78                 recfile.Field{Name: "Type", Value: DepTypeStamp},
79                 recfile.Field{Name: "Hash", Value: hsh.String()},
80         )
81 }
82
83 func fileHash(fd *os.File) (Hash, error) {
84         h := blake3.New(HashLen, nil)
85         if _, err := io.Copy(h, bufio.NewReader(fd)); err != nil {
86                 return "", err
87         }
88         return Hash(h.Sum(nil)), nil
89 }
90
91 func depWrite(fdDep *os.File, cwd string, tgt *Tgt, hsh Hash) error {
92         tracef(CDebug, "ifchange: %s <- %s", fdDep.Name(), tgt)
93         fd, err := os.Open(tgt.a)
94         if err != nil {
95                 return ErrLine(err)
96         }
97         defer fd.Close()
98         inode, isDir, err := inodeFromFileByFd(fd)
99         if err != nil {
100                 return ErrLine(err)
101         }
102         if isDir {
103                 return nil
104         }
105         if hsh == "" {
106                 hsh, err = fileHash(fd)
107                 if err != nil {
108                         return ErrLine(err)
109                 }
110         }
111         fields := []recfile.Field{
112                 {Name: "Type", Value: DepTypeIfchange},
113                 {Name: "Target", Value: tgt.RelTo(cwd)},
114                 {Name: "Hash", Value: hsh.String()},
115         }
116         fields = append(fields, inode.RecfileFields()...)
117         return recfileWrite(fdDep, fields...)
118 }
119
120 func depsWrite(fdDep *os.File, tgts []*Tgt) error {
121         if fdDep == nil {
122                 tracef(CDebug, "no opened fdDep: %s", tgts)
123                 return nil
124         }
125         var err error
126         var cwd string
127         for _, tgt := range tgts {
128                 cwd = Cwd
129                 if DepCwd != "" && Cwd != DepCwd {
130                         cwd = DepCwd
131                 }
132                 tgtDir := path.Join(cwd, DirPrefix)
133                 if _, errStat := os.Stat(tgt.a); errStat == nil {
134                         err = ErrLine(depWrite(fdDep, tgtDir, tgt, ""))
135                 } else {
136                         tgtRel := tgt.RelTo(tgtDir)
137                         tracef(CDebug, "ifchange: %s <- %s (non-existing)", fdDep.Name(), tgtRel)
138                         fields := []recfile.Field{
139                                 {Name: "Type", Value: DepTypeIfchange},
140                                 {Name: "Target", Value: tgtRel},
141                         }
142                         inodeDummy := Inode{}
143                         fields = append(fields, inodeDummy.RecfileFields()...)
144                         err = ErrLine(recfileWrite(fdDep, fields...))
145                 }
146                 if err != nil {
147                         return err
148                 }
149         }
150         return nil
151 }
152
153 type DepInfoIfchange struct {
154         tgt   *Tgt
155         inode *Inode
156         hash  Hash
157 }
158
159 type DepInfo struct {
160         build     string
161         always    bool
162         stamp     Hash
163         ifcreates []*Tgt
164         ifchanges []DepInfoIfchange
165 }
166
167 func mustHashDecode(s string) Hash {
168         b, err := hex.DecodeString(s)
169         if err != nil {
170                 log.Fatal(err)
171         }
172         return Hash(b)
173 }
174
175 var missingBuild = errors.New(".rec missing Build:")
176
177 func depRead(tgt *Tgt) (*DepInfo, error) {
178         data, err := os.ReadFile(tgt.Dep())
179         if err != nil {
180                 return nil, err
181         }
182         r := recfile.NewReader(bytes.NewReader(data))
183         m, err := r.NextMap()
184         if err != nil {
185                 return nil, err
186         }
187         depInfo := DepInfo{}
188         b := m["Build"]
189         if b == "" {
190                 return nil, missingBuild
191         }
192         depInfo.build = b
193         for {
194                 m, err := r.NextMap()
195                 if err != nil {
196                         if errors.Is(err, io.EOF) {
197                                 break
198                         }
199                         return nil, err
200                 }
201                 switch m["Type"] {
202                 case DepTypeAlways:
203                         depInfo.always = true
204                 case DepTypeIfcreate:
205                         dep := m["Target"]
206                         if dep == "" {
207                                 return nil, ErrBadRecFormat
208                         }
209                         depInfo.ifcreates = append(depInfo.ifcreates,
210                                 NewTgt(path.Join(tgt.h, dep)))
211                 case DepTypeIfchange:
212                         depRaw := m["Target"]
213                         if depRaw == "" {
214                                 return nil, ErrBadRecFormat
215                         }
216                         inode, err := inodeFromRec(m)
217                         if err != nil {
218                                 log.Print(err)
219                                 return nil, ErrBadRecFormat
220                         }
221                         dep := NewTgt(path.Join(tgt.h, depRaw))
222
223                         cachedFound := false
224                         for _, cachedInode := range InodeCache[dep.a] {
225                                 if inode.Equals(cachedInode) {
226                                         inode = cachedInode
227                                         cachedFound = true
228                                         break
229                                 }
230                         }
231                         if InodeCache != nil && !cachedFound {
232                                 InodeCache[dep.a] = append(InodeCache[dep.a], inode)
233                         }
234
235                         hsh := mustHashDecode(m["Hash"])
236                         cachedFound = false
237                         for _, cachedHash := range HashCache[dep.a] {
238                                 if hsh == cachedHash {
239                                         hsh = cachedHash
240                                         cachedFound = true
241                                         break
242                                 }
243                         }
244                         if HashCache != nil && !cachedFound {
245                                 HashCache[dep.a] = append(HashCache[dep.a], hsh)
246                         }
247
248                         depInfo.ifchanges = append(depInfo.ifchanges, DepInfoIfchange{
249                                 tgt: dep, inode: inode, hash: hsh,
250                         })
251                 case DepTypeStamp:
252                         hsh := m["Hash"]
253                         if hsh == "" {
254                                 return nil, ErrBadRecFormat
255                         }
256                         depInfo.stamp = mustHashDecode(hsh)
257                 default:
258                         return nil, ErrBadRecFormat
259                 }
260         }
261         return &depInfo, nil
262 }
263
264 func depReadOnlyIfchanges(pth string) (ifchanges []string, err error) {
265         data, err := os.ReadFile(pth)
266         if err != nil {
267                 return
268         }
269         r := recfile.NewReader(bytes.NewReader(data))
270         var m map[string]string
271         for {
272                 m, err = r.NextMap()
273                 if err != nil {
274                         if errors.Is(err, io.EOF) {
275                                 err = nil
276                                 break
277                         }
278                         return
279                 }
280                 if m["Type"] == DepTypeIfchange {
281                         ifchanges = append(ifchanges, m["Target"])
282                 }
283         }
284         return
285 }
286
287 func depReadBuild(pth string) (string, error) {
288         fd, err := os.Open(pth)
289         if err != nil {
290                 return "", err
291         }
292         r := recfile.NewReader(fd)
293         m, err := r.NextMap()
294         fd.Close()
295         if err != nil {
296                 return "", err
297         }
298         build := m["Build"]
299         if build == "" {
300                 err = missingBuild
301         }
302         return build, err
303 }