131 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			131 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Go
		
	
	
	
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
 | 
						|
}
 |