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