1// Copyright 2020 The SwiftShader Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// check_build_files scans all the .bp, .gn and .bazel files for source
16// references to non-existent files.
17
18package main
19
20import (
21	"flag"
22	"fmt"
23	"io/ioutil"
24	"os"
25	"path/filepath"
26	"regexp"
27	"strings"
28)
29
30func cwd() string {
31	wd, err := os.Getwd()
32	if err != nil {
33		return ""
34	}
35	return wd
36}
37
38var root = flag.String("root", cwd(), "root project directory")
39
40func main() {
41	flag.Parse()
42
43	if err := run(); err != nil {
44		fmt.Fprintf(os.Stderr, "%v", err)
45		os.Exit(1)
46	}
47	fmt.Printf("Build file check completed with no errors\n")
48}
49
50func run() error {
51	wd := *root
52
53	errs := []error{}
54
55	filepath.Walk(wd, func(path string, info os.FileInfo, err error) error {
56		if err != nil {
57			return err
58		}
59
60		rel, err := filepath.Rel(wd, path)
61		if err != nil {
62			return filepath.SkipDir
63		}
64
65		switch rel {
66		case ".git", "cache", "build", "out", "third_party":
67			return filepath.SkipDir
68		}
69
70		if info.IsDir() {
71			return nil
72		}
73
74		content, err := ioutil.ReadFile(path)
75		if err != nil {
76			errs = append(errs, err)
77			return nil // Continue walking files
78		}
79
80		switch filepath.Ext(path) {
81		case ".bp":
82			errs = append(errs, checkBlueprint(path, string(content))...)
83		case ".gn":
84			errs = append(errs, checkGn(path, string(content))...)
85		case ".bazel":
86			errs = append(errs, checkBazel(path, string(content))...)
87		}
88
89		return nil
90	})
91
92	sb := strings.Builder{}
93	for _, err := range errs {
94		sb.WriteString(err.Error())
95		sb.WriteString("\n")
96	}
97	if sb.Len() > 0 {
98		return fmt.Errorf("%v", sb.String())
99	}
100	return nil
101}
102
103var (
104	reSources = regexp.MustCompile(`sources\s*=\s*\[([^\]]*)\]`)
105	reSrc     = regexp.MustCompile(`srcs\s*[:=]\s*\[([^\]]*)\]`)
106	reQuoted  = regexp.MustCompile(`"([^\"]*)"`)
107)
108
109func checkBlueprint(path, content string) []error {
110	errs := []error{}
111	for _, sources := range matchRE(reSrc, content) {
112		for _, source := range matchRE(reQuoted, sources) {
113			if strings.HasPrefix(source, ":") {
114				continue // Build target, we can't resolve.
115			}
116			if err := checkSource(path, source); err != nil {
117				errs = append(errs, err)
118			}
119		}
120	}
121	return errs
122}
123
124func checkGn(path, content string) []error {
125	errs := []error{}
126	for _, sources := range matchRE(reSources, content) {
127		for _, source := range matchRE(reQuoted, sources) {
128			if strings.ContainsAny(source, "$") {
129				return nil // Env vars we can't resolve
130			}
131			if strings.HasPrefix(source, "//") {
132				continue // Build target, we can't resolve.
133			}
134			if err := checkSource(path, source); err != nil {
135				errs = append(errs, err)
136			}
137		}
138	}
139	return errs
140}
141
142func checkBazel(path, content string) []error {
143	errs := []error{}
144	for _, sources := range matchRE(reSrc, content) {
145		for _, source := range matchRE(reQuoted, sources) {
146			if strings.HasPrefix(source, "@") || strings.HasPrefix(source, ":") {
147				continue // Build target, we can't resolve.
148			}
149			if err := checkSource(path, source); err != nil {
150				errs = append(errs, err)
151			}
152		}
153	}
154	return errs
155}
156
157func checkSource(path, source string) error {
158	source = filepath.Join(filepath.Dir(path), source)
159
160	if strings.Contains(source, "*") {
161		sources, err := filepath.Glob(source)
162		if err != nil {
163			return fmt.Errorf("In '%v': %w", path, err)
164		}
165		if len(sources) == 0 {
166			return fmt.Errorf("In '%v': Glob '%v' does not reference any files", path, source)
167		}
168		return nil
169	}
170
171	stat, err := os.Stat(source)
172	if err != nil {
173		return fmt.Errorf("In '%v': %w", path, err)
174	}
175	if stat.IsDir() {
176		return fmt.Errorf("In '%v': '%v' refers to a directory, not a file", path, source)
177	}
178	return nil
179}
180
181func matchRE(re *regexp.Regexp, text string) []string {
182	out := []string{}
183	for _, match := range re.FindAllStringSubmatch(text, -1) {
184		if len(match) < 2 {
185			return nil
186		}
187		out = append(out, match[1])
188	}
189	return out
190}
191