mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-09 13:38:00 +00:00
resolver: Remove error codes
We've decided that these will be omitted for now. Move the check-spec-examples script into the tools/src directory, and update the go modules. Add a bash script to build and run this. Change-Id: I852f8ddb1b9b987410a2a49cf6d14e54c3cf3f0e Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/56381 Auto-Submit: Ben Clayton <bclayton@google.com> Reviewed-by: Antonio Maiorano <amaiorano@google.com> Commit-Queue: Ben Clayton <bclayton@google.com> Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
committed by
Tint LUCI CQ
parent
91622e3853
commit
5a88ec8822
33
tools/check-spec-examples
Executable file
33
tools/check-spec-examples
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright 2021 The Tint Authors
|
||||
#
|
||||
# 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.
|
||||
|
||||
set -e # Fail on any error.
|
||||
|
||||
if [ ! -x "$(which go)" ] ; then
|
||||
echo "error: go needs to be on \$PATH to use $0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )"
|
||||
ROOT_DIR="$( cd "${SCRIPT_DIR}/.." >/dev/null 2>&1 && pwd )"
|
||||
BINARY="${SCRIPT_DIR}/bin/check-spec-examples"
|
||||
|
||||
# Rebuild the binary.
|
||||
# Note, go caches build artifacts, so this is quick for repeat calls
|
||||
pushd "${SCRIPT_DIR}/src/cmd/check-spec-examples" > /dev/null
|
||||
go build -o "${BINARY}" main.go
|
||||
popd > /dev/null
|
||||
|
||||
"${BINARY}" "$@"
|
||||
@@ -1,312 +0,0 @@
|
||||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// 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-spec-examples tests that WGSL specification examples compile as
|
||||
// expected.
|
||||
//
|
||||
// The tool parses the WGSL HTML specification from the web or from a local file
|
||||
// and then runs the WGSL compiler for all examples annotated with the 'wgsl'
|
||||
// and 'global-scope' or 'function-scope' HTML class types.
|
||||
//
|
||||
// To run:
|
||||
// go get golang.org/x/net/html # Only required once
|
||||
// go run tools/check-spec-examples/main.go --compiler=<path-to-tint>
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
const (
|
||||
toolName = "check-spec-examples"
|
||||
defaultSpecPath = "https://gpuweb.github.io/gpuweb/wgsl.html"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidArg = errors.New("Invalid arguments")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
out := flag.CommandLine.Output()
|
||||
fmt.Fprintf(out, "%v tests that WGSL specification examples compile as expected.\n", toolName)
|
||||
fmt.Fprintf(out, "\n")
|
||||
fmt.Fprintf(out, "Usage:\n")
|
||||
fmt.Fprintf(out, " %s [spec] [flags]\n", toolName)
|
||||
fmt.Fprintf(out, "\n")
|
||||
fmt.Fprintf(out, "spec is an optional local file path or URL to the WGSL specification.\n")
|
||||
fmt.Fprintf(out, "If spec is omitted then the specification is fetched from %v\n", defaultSpecPath)
|
||||
fmt.Fprintf(out, "\n")
|
||||
fmt.Fprintf(out, "flags may be any combination of:\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
err := run()
|
||||
switch err {
|
||||
case nil:
|
||||
return
|
||||
case errInvalidArg:
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n\n", err)
|
||||
flag.Usage()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func run() error {
|
||||
// Parse flags
|
||||
compilerPath := flag.String("compiler", "tint", "path to compiler executable")
|
||||
verbose := flag.Bool("verbose", false, "print examples that pass")
|
||||
flag.Parse()
|
||||
|
||||
// Try to find the WGSL compiler
|
||||
compiler, err := exec.LookPath(*compilerPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to find WGSL compiler: %w", err)
|
||||
}
|
||||
if compiler, err = filepath.Abs(compiler); err != nil {
|
||||
return fmt.Errorf("Failed to find WGSL compiler: %w", err)
|
||||
}
|
||||
|
||||
// Check for explicit WGSL spec path
|
||||
args := flag.Args()
|
||||
specURL, _ := url.Parse(defaultSpecPath)
|
||||
switch len(args) {
|
||||
case 0:
|
||||
case 1:
|
||||
var err error
|
||||
specURL, err = url.Parse(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if len(args) > 1 {
|
||||
return errInvalidArg
|
||||
}
|
||||
}
|
||||
|
||||
// The specURL might just be a local file path, in which case automatically
|
||||
// add the 'file' URL scheme
|
||||
if specURL.Scheme == "" {
|
||||
specURL.Scheme = "file"
|
||||
}
|
||||
|
||||
// Open the spec from HTTP(S) or from a local file
|
||||
var specContent io.ReadCloser
|
||||
switch specURL.Scheme {
|
||||
case "http", "https":
|
||||
response, err := http.Get(specURL.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load the WGSL spec from '%v': %w", specURL, err)
|
||||
}
|
||||
specContent = response.Body
|
||||
case "file":
|
||||
specURL.Path, err = filepath.Abs(specURL.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load the WGSL spec from '%v': %w", specURL, err)
|
||||
}
|
||||
|
||||
file, err := os.Open(specURL.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load the WGSL spec from '%v': %w", specURL, err)
|
||||
}
|
||||
specContent = file
|
||||
default:
|
||||
return fmt.Errorf("Unsupported URL scheme: %v", specURL.Scheme)
|
||||
}
|
||||
defer specContent.Close()
|
||||
|
||||
// Create the HTML parser
|
||||
doc, err := html.Parse(specContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse all the WGSL examples
|
||||
examples := []example{}
|
||||
if err := gatherExamples(doc, &examples); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a temporary directory to hold the examples as separate files
|
||||
tmpDir, err := ioutil.TempDir("", "wgsl-spec-examples")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(tmpDir, 0666); err != nil {
|
||||
return fmt.Errorf("Failed to create temporary directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// For each compilable WGSL example...
|
||||
for _, e := range examples {
|
||||
exampleURL := specURL.String() + "#" + e.name
|
||||
|
||||
if err := tryCompile(compiler, tmpDir, e); err != nil {
|
||||
if !e.expectError {
|
||||
fmt.Printf("✘ %v ✘\n%v\n", exampleURL, err)
|
||||
continue
|
||||
}
|
||||
} else if e.expectError {
|
||||
fmt.Printf("✘ %v ✘\nCompiled even though it was marked with 'expect-error'\n", exampleURL)
|
||||
}
|
||||
if *verbose {
|
||||
fmt.Printf("✔ %v ✔\n", exampleURL)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Holds all the information about a single, compilable WGSL example in the spec
|
||||
type example struct {
|
||||
name string // The name (typically hash generated by bikeshed)
|
||||
code string // The example source
|
||||
globalScope bool // Annotated with 'global-scope' ?
|
||||
functionScope bool // Annotated with 'function-scope' ?
|
||||
expectError bool // Annotated with 'expect-error' ?
|
||||
}
|
||||
|
||||
// tryCompile attempts to compile the example e in the directory wd, using the
|
||||
// compiler at the given path. If the example is annotated with 'function-scope'
|
||||
// then the code is wrapped with a basic vertex-stage-entry function.
|
||||
// If the first compile fails with an error message containing 'error v-0003',
|
||||
// then a dummy vertex-state-entry function is appended to the source, and
|
||||
// another attempt to compile the shader is made.
|
||||
func tryCompile(compiler, wd string, e example) error {
|
||||
code := e.code
|
||||
if e.functionScope {
|
||||
code = "\n[[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> {\n" + code + " return vec4<f32>();}\n"
|
||||
}
|
||||
|
||||
addedStubFunction := false
|
||||
for {
|
||||
err := compile(compiler, wd, e.name, code)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !addedStubFunction && strings.Contains(err.Error(), "error v-0003") {
|
||||
// error v-0003: At least one of vertex, fragment or compute shader
|
||||
// must be present. Add a stub entry point to satisfy the compiler.
|
||||
code += "\n[[stage(vertex)]] fn main() {}\n"
|
||||
addedStubFunction = true
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// compile creates a file in wd and uses the compiler to attempt to compile it.
|
||||
func compile(compiler, wd, name, code string) error {
|
||||
filename := name + ".wgsl"
|
||||
path := filepath.Join(wd, filename)
|
||||
if err := ioutil.WriteFile(path, []byte(code), 0666); err != nil {
|
||||
return fmt.Errorf("Failed to write example file '%v'", path)
|
||||
}
|
||||
cmd := exec.Command(compiler, filename)
|
||||
cmd.Dir = wd
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v\n%v", err, string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// gatherExamples scans the HTML node and its children for blocks that contain
|
||||
// WGSL example code, populating the examples slice.
|
||||
func gatherExamples(node *html.Node, examples *[]example) error {
|
||||
if hasClass(node, "example") && hasClass(node, "wgsl") {
|
||||
e := example{
|
||||
name: nodeID(node),
|
||||
code: exampleCode(node),
|
||||
globalScope: hasClass(node, "global-scope"),
|
||||
functionScope: hasClass(node, "function-scope"),
|
||||
expectError: hasClass(node, "expect-error"),
|
||||
}
|
||||
// If the example is annotated with a scope, then it can be compiled.
|
||||
if e.globalScope || e.functionScope {
|
||||
*examples = append(*examples, e)
|
||||
}
|
||||
}
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
if err := gatherExamples(child, examples); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// exampleCode returns a string formed from all the TextNodes found in <pre>
|
||||
// blocks that are children of node.
|
||||
func exampleCode(node *html.Node) string {
|
||||
sb := strings.Builder{}
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
if child.Data == "pre" {
|
||||
printNodeText(child, &sb)
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// printNodeText traverses node and its children, writing the Data of all
|
||||
// TextNodes to sb.
|
||||
func printNodeText(node *html.Node, sb *strings.Builder) {
|
||||
if node.Type == html.TextNode {
|
||||
sb.WriteString(node.Data)
|
||||
}
|
||||
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
printNodeText(child, sb)
|
||||
}
|
||||
}
|
||||
|
||||
// hasClass returns true iff node is has the given "class" attribute.
|
||||
func hasClass(node *html.Node, class string) bool {
|
||||
for _, attr := range node.Attr {
|
||||
if attr.Key == "class" {
|
||||
classes := strings.Split(attr.Val, " ")
|
||||
for _, c := range classes {
|
||||
if c == class {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// nodeID returns the given "id" attribute of node, or an empty string if there
|
||||
// is no "id" attribute.
|
||||
func nodeID(node *html.Node) string {
|
||||
for _, attr := range node.Attr {
|
||||
if attr.Key == "id" {
|
||||
return attr.Val
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user