| package canoninja |
| |
| import ( |
| "bytes" |
| "crypto/sha1" |
| "encoding/hex" |
| "fmt" |
| "io" |
| ) |
| |
| var ( |
| rulePrefix = []byte("rule ") |
| buildPrefix = []byte("build ") |
| phonyRule = []byte("phony") |
| ) |
| |
| func Generate(path string, buffer []byte, sink io.Writer) error { |
| // Break file into lines |
| from := 0 |
| var lines [][]byte |
| for from < len(buffer) { |
| line := getLine(buffer[from:]) |
| lines = append(lines, line) |
| from += len(line) |
| } |
| |
| // FOr each rule, calculate and remember its digest |
| ruleDigest := make(map[string]string) |
| for i := 0; i < len(lines); { |
| if bytes.HasPrefix(lines[i], rulePrefix) { |
| // Find ruleName |
| rn := ruleName(lines[i]) |
| if len(rn) == 0 { |
| return fmt.Errorf("%s:%d: rule name is missing or on the next line", path, i+1) |
| } |
| sRuleName := string(rn) |
| if _, ok := ruleDigest[sRuleName]; ok { |
| return fmt.Errorf("%s:%d: the rule %s has been already defined", path, i+1, sRuleName) |
| } |
| // Calculate rule text digest as a digests of line digests. |
| var digests []byte |
| doDigest := func(b []byte) { |
| h := sha1.New() |
| h.Write(b) |
| digests = h.Sum(digests) |
| |
| } |
| // For the first line, digest everything after rule's name |
| doDigest(lines[i][cap(lines[i])+len(rn)-cap(rn):]) |
| for i++; i < len(lines) && lines[i][0] == ' '; i++ { |
| doDigest(lines[i]) |
| } |
| h := sha1.New() |
| h.Write(digests) |
| ruleDigest[sRuleName] = "R" + hex.EncodeToString(h.Sum(nil)) |
| |
| } else { |
| i++ |
| } |
| } |
| |
| // Rewrite rule names. |
| for i, line := range lines { |
| if bytes.HasPrefix(line, buildPrefix) { |
| brn := getBuildRuleName(line) |
| if bytes.Equal(brn, phonyRule) { |
| sink.Write(line) |
| continue |
| } |
| if len(brn) == 0 { |
| return fmt.Errorf("%s:%d: build statement lacks rule name", path, i+1) |
| } |
| sink.Write(line[0 : cap(line)-cap(brn)]) |
| if digest, ok := ruleDigest[string(brn)]; ok { |
| sink.Write([]byte(digest)) |
| } else { |
| return fmt.Errorf("%s:%d: no rule for this build target", path, i+1) |
| } |
| sink.Write(line[cap(line)+len(brn)-cap(brn):]) |
| } else if bytes.HasPrefix(line, rulePrefix) { |
| rn := ruleName(line) |
| // Write everything before it |
| sink.Write(line[0 : cap(line)-cap(rn)]) |
| sink.Write([]byte(ruleDigest[string(rn)])) |
| sink.Write(line[cap(line)+len(rn)-cap(rn):]) |
| } else { |
| //goland:noinspection GoUnhandledErrorResult |
| sink.Write(line) |
| } |
| } |
| return nil |
| } |
| |
| func getLine(b []byte) []byte { |
| if n := bytes.IndexByte(b, '\n'); n >= 0 { |
| return b[:n+1] |
| } |
| return b |
| } |
| |
| // Returns build statement's rule name |
| func getBuildRuleName(line []byte) []byte { |
| n := bytes.IndexByte(line, ':') |
| if n <= 0 { |
| return nil |
| } |
| ruleName := line[n+1:] |
| if ruleName[0] == ' ' { |
| ruleName = bytes.TrimLeft(ruleName, " ") |
| } |
| if n := bytes.IndexAny(ruleName, " \t\r\n"); n >= 0 { |
| ruleName = ruleName[0:n] |
| } |
| return ruleName |
| } |
| |
| // Returns rule statement's rule name |
| func ruleName(lineAfterRule []byte) []byte { |
| ruleName := lineAfterRule[len(rulePrefix):] |
| if len(ruleName) == 0 { |
| return ruleName |
| } |
| if ruleName[0] == ' ' { |
| ruleName = bytes.TrimLeft(ruleName, " ") |
| } |
| if n := bytes.IndexAny(ruleName, " \t\r\n"); n >= 0 { |
| ruleName = ruleName[0:n] |
| } |
| return ruleName |
| } |