| // Copyright 2020 The SwiftShader Authors. All Rights Reserved. | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //    http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | // check_build_files scans all the .bp, .gn and .bazel files for source | 
 | // references to non-existent files. | 
 |  | 
 | package main | 
 |  | 
 | import ( | 
 | 	"flag" | 
 | 	"fmt" | 
 | 	"io/ioutil" | 
 | 	"os" | 
 | 	"path/filepath" | 
 | 	"regexp" | 
 | 	"strings" | 
 | ) | 
 |  | 
 | func cwd() string { | 
 | 	wd, err := os.Getwd() | 
 | 	if err != nil { | 
 | 		return "" | 
 | 	} | 
 | 	return wd | 
 | } | 
 |  | 
 | var root = flag.String("root", cwd(), "root project directory") | 
 |  | 
 | func main() { | 
 | 	flag.Parse() | 
 |  | 
 | 	if err := run(); err != nil { | 
 | 		fmt.Fprintf(os.Stderr, "%v", err) | 
 | 		os.Exit(1) | 
 | 	} | 
 | 	fmt.Printf("Build file check completed with no errors\n") | 
 | } | 
 |  | 
 | func run() error { | 
 | 	wd := *root | 
 |  | 
 | 	errs := []error{} | 
 |  | 
 | 	filepath.Walk(wd, func(path string, info os.FileInfo, err error) error { | 
 | 		if err != nil { | 
 | 			return err | 
 | 		} | 
 |  | 
 | 		rel, err := filepath.Rel(wd, path) | 
 | 		if err != nil { | 
 | 			return filepath.SkipDir | 
 | 		} | 
 |  | 
 | 		switch rel { | 
 | 		case ".git", "cache", "build", "out", "third_party": | 
 | 			return filepath.SkipDir | 
 | 		} | 
 |  | 
 | 		if info.IsDir() { | 
 | 			return nil | 
 | 		} | 
 |  | 
 | 		content, err := ioutil.ReadFile(path) | 
 | 		if err != nil { | 
 | 			errs = append(errs, err) | 
 | 			return nil // Continue walking files | 
 | 		} | 
 |  | 
 | 		switch filepath.Ext(path) { | 
 | 		case ".bp": | 
 | 			errs = append(errs, checkBlueprint(path, string(content))...) | 
 | 		case ".gn": | 
 | 			errs = append(errs, checkGn(path, string(content))...) | 
 | 		case ".bazel": | 
 | 			errs = append(errs, checkBazel(path, string(content))...) | 
 | 		} | 
 |  | 
 | 		return nil | 
 | 	}) | 
 |  | 
 | 	sb := strings.Builder{} | 
 | 	for _, err := range errs { | 
 | 		sb.WriteString(err.Error()) | 
 | 		sb.WriteString("\n") | 
 | 	} | 
 | 	if sb.Len() > 0 { | 
 | 		return fmt.Errorf("%v", sb.String()) | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | var ( | 
 | 	reSources = regexp.MustCompile(`sources\s*=\s*\[([^\]]*)\]`) | 
 | 	reSrc     = regexp.MustCompile(`srcs\s*[:=]\s*\[([^\]]*)\]`) | 
 | 	reQuoted  = regexp.MustCompile(`"([^\"]*)"`) | 
 | ) | 
 |  | 
 | func checkBlueprint(path, content string) []error { | 
 | 	errs := []error{} | 
 | 	for _, sources := range matchRE(reSrc, content) { | 
 | 		for _, source := range matchRE(reQuoted, sources) { | 
 | 			if strings.HasPrefix(source, ":") { | 
 | 				continue // Build target, we can't resolve. | 
 | 			} | 
 | 			if err := checkSource(path, source); err != nil { | 
 | 				errs = append(errs, err) | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	return errs | 
 | } | 
 |  | 
 | func checkGn(path, content string) []error { | 
 | 	errs := []error{} | 
 | 	for _, sources := range matchRE(reSources, content) { | 
 | 		for _, source := range matchRE(reQuoted, sources) { | 
 | 			if strings.ContainsAny(source, "$") { | 
 | 				return nil // Env vars we can't resolve | 
 | 			} | 
 | 			if strings.HasPrefix(source, "//") { | 
 | 				continue // Build target, we can't resolve. | 
 | 			} | 
 | 			if err := checkSource(path, source); err != nil { | 
 | 				errs = append(errs, err) | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	return errs | 
 | } | 
 |  | 
 | func checkBazel(path, content string) []error { | 
 | 	errs := []error{} | 
 | 	for _, sources := range matchRE(reSrc, content) { | 
 | 		for _, source := range matchRE(reQuoted, sources) { | 
 | 			if strings.HasPrefix(source, "@") || strings.HasPrefix(source, ":") { | 
 | 				continue // Build target, we can't resolve. | 
 | 			} | 
 | 			if err := checkSource(path, source); err != nil { | 
 | 				errs = append(errs, err) | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	return errs | 
 | } | 
 |  | 
 | func checkSource(path, source string) error { | 
 | 	source = filepath.Join(filepath.Dir(path), source) | 
 |  | 
 | 	if strings.Contains(source, "*") { | 
 | 		sources, err := filepath.Glob(source) | 
 | 		if err != nil { | 
 | 			return fmt.Errorf("In '%v': %w", path, err) | 
 | 		} | 
 | 		if len(sources) == 0 { | 
 | 			return fmt.Errorf("In '%v': Glob '%v' does not reference any files", path, source) | 
 | 		} | 
 | 		return nil | 
 | 	} | 
 |  | 
 | 	stat, err := os.Stat(source) | 
 | 	if err != nil { | 
 | 		return fmt.Errorf("In '%v': %w", path, err) | 
 | 	} | 
 | 	if stat.IsDir() { | 
 | 		return fmt.Errorf("In '%v': '%v' refers to a directory, not a file", path, source) | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | func matchRE(re *regexp.Regexp, text string) []string { | 
 | 	out := []string{} | 
 | 	for _, match := range re.FindAllStringSubmatch(text, -1) { | 
 | 		if len(match) < 2 { | 
 | 			return nil | 
 | 		} | 
 | 		out = append(out, match[1]) | 
 | 	} | 
 | 	return out | 
 | } |