Kokoro: Check that build files don't reference non-existent files
This is a primitive check to ensure that .bp, .gn and .bazel files don't have source references to missing files.
This can catch a common build breakage.
Fixes: b/153439736
Change-Id: I95621f8775a0e536015d4da9897785935f130884
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/43572
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
diff --git a/tests/check_build_files/main.go b/tests/check_build_files/main.go
new file mode 100644
index 0000000..8ab972d
--- /dev/null
+++ b/tests/check_build_files/main.go
@@ -0,0 +1,190 @@
+// 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":
+ 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
+}
diff --git a/tests/presubmit.sh b/tests/presubmit.sh
index 122c51c..810309b 100755
--- a/tests/presubmit.sh
+++ b/tests/presubmit.sh
@@ -80,6 +80,10 @@
find ${SRC_DIR} ${TESTS_DIR} -name "*.go" | xargs $GOFMT -w
}
+function run_check_build_files() {
+ go run ${TESTS_DIR}/check_build_files/main.go --root="${ROOT_DIR}"
+}
+
# Ensure we are clean to start out with.
check "git workspace must be clean" true
@@ -95,5 +99,8 @@
# Check gofmt.
check gofmt run_gofmt
+# Check build files.
+check "build files don't reference non-existent files" run_check_build_files
+
echo
echo "${green}All check completed successfully.${normal}"