[tint][gen] Detect #includes without necessary guards
This prevents unguarded includes to targets that require TINT_BUILD_XXX
flags to be enabled.
Change-Id: I604b473a9cb24044346e8ef14f841851570b0086
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/155443
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/docs/tint/gen.md b/docs/tint/gen.md
index 7d48ac6..14a7907 100644
--- a/docs/tint/gen.md
+++ b/docs/tint/gen.md
@@ -126,6 +126,8 @@
{
/* An override for the output file name for the target */
"OutputName": "name",
+ /* An additional condition for building this target */
+ "Condition": "cond",
"AdditionalDependencies": {
"Internal": [
/*
diff --git a/tools/src/cmd/gen/build/build.go b/tools/src/cmd/gen/build/build.go
index 15e17b5..7ab9bfe 100644
--- a/tools/src/cmd/gen/build/build.go
+++ b/tools/src/cmd/gen/build/build.go
@@ -209,23 +209,63 @@
// parseFile parses the source file at 'path' represented by 'file'
// As this is run concurrently, it must not modify any shared state (including file)
parseFile := func(path string, file *File) (string, *ParsedFile, error) {
+ conditions := []Condition{}
+
body, err := os.ReadFile(file.AbsPath())
if err != nil {
return path, nil, err
}
out := &ParsedFile{}
for i, line := range strings.Split(string(body), "\n") {
+ wrapErr := func(err error) error {
+ return fmt.Errorf("%v:%v %w", file.Path(), i+1, err)
+ }
if match := reIgnoreFile.FindStringSubmatch(line); len(match) > 0 {
out.removeFromProject = true
continue
}
+ if match := reIf.FindStringSubmatch(line); len(match) > 0 {
+ condition, err := cnf.Parse(strings.ToLower(match[1]))
+ if err != nil {
+ condition = Condition{{cnf.Unary{Var: "FAILED_TO_PARSE_CONDITION"}}}
+ }
+ if len(conditions) > 0 {
+ condition = cnf.And(condition, conditions[len(conditions)-1])
+ }
+ conditions = append(conditions, condition)
+ }
+ if match := reIfdef.FindStringSubmatch(line); len(match) > 0 {
+ conditions = append(conditions, Condition{})
+ }
+ if match := reIfndef.FindStringSubmatch(line); len(match) > 0 {
+ conditions = append(conditions, Condition{})
+ }
+ if match := reElse.FindStringSubmatch(line); len(match) > 0 {
+ if len(conditions) == 0 {
+ return path, nil, wrapErr(fmt.Errorf("#else without #if"))
+ }
+ conditions[len(conditions)-1] = cnf.Not(conditions[len(conditions)-1])
+ }
+ if match := reEndif.FindStringSubmatch(line); len(match) > 0 {
+ if len(conditions) == 0 {
+ return path, nil, wrapErr(fmt.Errorf("#endif without #if"))
+ }
+ conditions = conditions[:len(conditions)-1]
+ }
if match := reCondition.FindStringSubmatch(line); len(match) > 0 {
out.conditions = append(out.conditions, match[1])
}
if !reIgnoreInclude.MatchString(line) {
for _, re := range []*regexp.Regexp{reInclude, reHashImport, reAtImport} {
if match := re.FindStringSubmatch(line); len(match) > 0 {
- out.includes = append(out.includes, Include{match[1], i + 1})
+ include := Include{
+ Path: match[1],
+ Line: i + 1,
+ }
+ if len(conditions) > 0 {
+ include.Condition = conditions[len(conditions)-1]
+ }
+ out.includes = append(out.includes, include)
}
}
}
@@ -322,6 +362,14 @@
// Apply any custom output name
target.OutputName = tc.cfg.OutputName
+ if tc.cfg.Condition != "" {
+ condition, err := cnf.Parse(tc.cfg.Condition)
+ if err != nil {
+ return fmt.Errorf("%v: %v", path, err)
+ }
+ target.Condition = cnf.And(target.Condition, condition)
+ }
+
// Add any additional internal dependencies
for _, depPattern := range tc.cfg.AdditionalDependencies.Internal {
match, err := match.New(depPattern)
@@ -424,6 +472,25 @@
addExternalDependency(dependency)
}
+ noneIfEmpty := func(cond Condition) string {
+ if len(cond) == 0 {
+ return "<none>"
+ }
+ return cond.String()
+ }
+ sourceConditions := cnf.And(cnf.And(include.Condition, file.Condition), file.Target.Condition)
+ targetConditions := cnf.And(includeFile.Condition, includeFile.Target.Condition)
+ if missing := targetConditions.Remove(sourceConditions); len(missing) > 0 {
+ return fmt.Errorf(`%v:%v #include "%v" requires guard: #if %v
+
+%v build conditions: %v
+%v build conditions: %v`,
+ file.Path(), include.Line, include.Path, strings.ToUpper(missing.String()),
+ file.Path(), noneIfEmpty(sourceConditions),
+ include.Path, targetConditions,
+ )
+ }
+
} else {
// Check for external includes
for _, external := range p.externals.Values() {
@@ -647,6 +714,11 @@
var (
// Regular expressions used by this file
+ reIf = regexp.MustCompile(`\s*#\s*if\s+(.*)`)
+ reIfdef = regexp.MustCompile(`\s*#\s*ifdef\s+(.*)`)
+ reIfndef = regexp.MustCompile(`\s*#\s*ifndef\s+(.*)`)
+ reElse = regexp.MustCompile(`\s*#\s*else\s+(.*)`)
+ reEndif = regexp.MustCompile(`\s*#\s*endif`)
reInclude = regexp.MustCompile(`\s*#\s*include\s*(?:\"|<)([^(\"|>)]+)(?:\"|>)`)
reHashImport = regexp.MustCompile(`\s*#\s*import\s*\<([\w\/\.]+)\>`)
reAtImport = regexp.MustCompile(`\s*@\s*import\s*(\w+)\s*;`)
diff --git a/tools/src/cmd/gen/build/directory_config.go b/tools/src/cmd/gen/build/directory_config.go
index 4548ad6..666a231 100644
--- a/tools/src/cmd/gen/build/directory_config.go
+++ b/tools/src/cmd/gen/build/directory_config.go
@@ -18,6 +18,8 @@
type TargetConfig struct {
// Override for the output name of this target
OutputName string
+ // Conditionals for this target
+ Condition string
// Additional dependencies to add to this target
AdditionalDependencies struct {
// List of internal dependency patterns
diff --git a/tools/src/cmd/gen/build/file.go b/tools/src/cmd/gen/build/file.go
index 7fb9b9c..98b5bf1 100644
--- a/tools/src/cmd/gen/build/file.go
+++ b/tools/src/cmd/gen/build/file.go
@@ -18,8 +18,9 @@
// Include describes a single #include in a file
type Include struct {
- Path string
- Line int
+ Path string
+ Line int
+ Condition Condition
}
// File holds information about a source file
diff --git a/tools/src/cnf/expr.go b/tools/src/cnf/expr.go
index 2466901..cdd5853 100644
--- a/tools/src/cnf/expr.go
+++ b/tools/src/cnf/expr.go
@@ -14,6 +14,8 @@
package cnf
+import "dawn.googlesource.com/dawn/tools/src/container"
+
// Expr is a boolean expression, expressed in a Conjunctive Normal Form.
// Expr is an alias to Ands, which represent all the OR expressions that are
// AND'd together.
@@ -38,3 +40,18 @@
// The name of the variable
Var string
}
+
+// Remove returns a new expression with all the And expressions of o removed from e
+func (e Expr) Remove(o Expr) Expr {
+ set := container.NewSet[Key]()
+ for _, expr := range o {
+ set.Add(expr.Key())
+ }
+ out := Expr{}
+ for _, expr := range e {
+ if !set.Contains(expr.Key()) {
+ out = append(out, expr)
+ }
+ }
+ return out
+}