blob: 7f2f5a5c08332dd8b0a9171ddc652fad171f3132 [file] [log] [blame]
// 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
}