xref: /aosp_15_r20/build/make/tools/canoninja/canoninja.go (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
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