blob: 681a69438c36672ca9c8018c162b08a55a3e3ebc [file] [log] [blame]
Sasha Smundak26c705f2021-09-30 18:13:54 -07001package canoninja
2
3import (
4 "bytes"
5 "crypto/sha1"
6 "encoding/hex"
7 "fmt"
8 "io"
9)
10
11var (
12 rulePrefix = []byte("rule ")
13 buildPrefix = []byte("build ")
14 phonyRule = []byte("phony")
15)
16
17func Generate(path string, buffer []byte, sink io.Writer) error {
18 // Break file into lines
19 from := 0
20 var lines [][]byte
21 for from < len(buffer) {
22 line := getLine(buffer[from:])
23 lines = append(lines, line)
24 from += len(line)
25 }
26
27 // FOr each rule, calculate and remember its digest
28 ruleDigest := make(map[string]string)
29 for i := 0; i < len(lines); {
30 if bytes.HasPrefix(lines[i], rulePrefix) {
31 // Find ruleName
32 rn := ruleName(lines[i])
33 if len(rn) == 0 {
34 return fmt.Errorf("%s:%d: rule name is missing or on the next line", path, i+1)
35 }
36 sRuleName := string(rn)
37 if _, ok := ruleDigest[sRuleName]; ok {
38 return fmt.Errorf("%s:%d: the rule %s has been already defined", path, i+1, sRuleName)
39 }
40 // Calculate rule text digest as a digests of line digests.
41 var digests []byte
42 doDigest := func(b []byte) {
43 h := sha1.New()
44 h.Write(b)
45 digests = h.Sum(digests)
46
47 }
48 // For the first line, digest everything after rule's name
49 doDigest(lines[i][cap(lines[i])+len(rn)-cap(rn):])
50 for i++; i < len(lines) && lines[i][0] == ' '; i++ {
51 doDigest(lines[i])
52 }
53 h := sha1.New()
54 h.Write(digests)
55 ruleDigest[sRuleName] = "R" + hex.EncodeToString(h.Sum(nil))
56
57 } else {
58 i++
59 }
60 }
61
62 // Rewrite rule names.
63 for i, line := range lines {
64 if bytes.HasPrefix(line, buildPrefix) {
65 brn := getBuildRuleName(line)
66 if bytes.Equal(brn, phonyRule) {
67 sink.Write(line)
68 continue
69 }
70 if len(brn) == 0 {
71 return fmt.Errorf("%s:%d: build statement lacks rule name", path, i+1)
72 }
73 sink.Write(line[0 : cap(line)-cap(brn)])
74 if digest, ok := ruleDigest[string(brn)]; ok {
75 sink.Write([]byte(digest))
76 } else {
77 return fmt.Errorf("%s:%d: no rule for this build target", path, i+1)
78 }
79 sink.Write(line[cap(line)+len(brn)-cap(brn):])
80 } else if bytes.HasPrefix(line, rulePrefix) {
81 rn := ruleName(line)
82 // Write everything before it
83 sink.Write(line[0 : cap(line)-cap(rn)])
84 sink.Write([]byte(ruleDigest[string(rn)]))
85 sink.Write(line[cap(line)+len(rn)-cap(rn):])
86 } else {
87 //goland:noinspection GoUnhandledErrorResult
88 sink.Write(line)
89 }
90 }
91 return nil
92}
93
94func getLine(b []byte) []byte {
95 if n := bytes.IndexByte(b, '\n'); n >= 0 {
96 return b[:n+1]
97 }
98 return b
99}
100
101// Returns build statement's rule name
102func getBuildRuleName(line []byte) []byte {
103 n := bytes.IndexByte(line, ':')
104 if n <= 0 {
105 return nil
106 }
107 ruleName := line[n+1:]
108 if ruleName[0] == ' ' {
109 ruleName = bytes.TrimLeft(ruleName, " ")
110 }
111 if n := bytes.IndexAny(ruleName, " \t\r\n"); n >= 0 {
112 ruleName = ruleName[0:n]
113 }
114 return ruleName
115}
116
117// Returns rule statement's rule name
118func ruleName(lineAfterRule []byte) []byte {
119 ruleName := lineAfterRule[len(rulePrefix):]
120 if len(ruleName) == 0 {
121 return ruleName
122 }
123 if ruleName[0] == ' ' {
124 ruleName = bytes.TrimLeft(ruleName, " ")
125 }
126 if n := bytes.IndexAny(ruleName, " \t\r\n"); n >= 0 {
127 ruleName = ruleName[0:n]
128 }
129 return ruleName
130}