1package 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} 131