[tools] Add `tools/run node` to run a .js file with dawn/node
Change-Id: I797919f73d2c01873cb708bb75c809cdd7b14d31
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/179820
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/tools/src/cmd/node/main.go b/tools/src/cmd/node/main.go
new file mode 100644
index 0000000..d2dbf54
--- /dev/null
+++ b/tools/src/cmd/node/main.go
@@ -0,0 +1,141 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ "dawn.googlesource.com/dawn/tools/src/cmd/run-cts/common"
+ "dawn.googlesource.com/dawn/tools/src/dawn/node"
+ "dawn.googlesource.com/dawn/tools/src/fileutils"
+)
+
+// main entry point
+func main() {
+ nodeFlags := node.Flags{}
+ opts := node.Options{
+ AllowUnsafeAPIs: true,
+ }
+
+ var nodePath string
+ lldb := false
+
+ flag.Usage = func() {
+ out := flag.CommandLine.Output()
+ fmt.Fprintf(out, "node runs a .js file with 'navigator.gpu' provided by dawn/node.\n\n")
+ flag.PrintDefaults()
+ }
+
+ flag.StringVar(&nodePath, "node", fileutils.NodePath(), "path to node executable")
+ flag.Var(&nodeFlags, "flag", "flag to pass to dawn-node as flag=value. multiple flags must be passed in individually")
+ flag.StringVar(&opts.BinDir, "bin", fileutils.BuildPath(), "path to the directory holding cts.js and dawn.node")
+ flag.StringVar(&opts.Backend, "backend", "default", "backend to use: default|null|webgpu|d3d11|d3d12|metal|vulkan|opengl|opengles."+
+ " set to 'vulkan' if VK_ICD_FILENAMES environment variable is set, 'default' otherwise")
+ flag.StringVar(&opts.Adapter, "adapter", "", "name (or substring) of the GPU adapter to use")
+ flag.BoolVar(&opts.Validate, "validate", false, "enable backend validation")
+ flag.BoolVar(&opts.DumpShaders, "dump-shaders", false, "dump WGSL shaders. Enables --verbose")
+ flag.BoolVar(&opts.UseFXC, "fxc", false, "Use FXC instead of DXC. Disables 'use_dxc' Dawn flag")
+ flag.BoolVar(&lldb, "lldb", false, "launch node via lldb")
+ flag.Parse()
+
+ nodeFlags.SetOptions(opts)
+
+ debugger := ""
+ if lldb {
+ debugger = "lldb"
+ }
+
+ if err := run(opts.BinDir, nodePath, nodeFlags, flag.Args(), debugger); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+}
+
+// run starts the
+func run(binPath, nodePath string, flags node.Flags, args []string, debugger string) error {
+ if len(args) == 0 {
+ return fmt.Errorf("missing path to .js file")
+ }
+
+ scriptPath, err := filepath.Abs(args[0])
+ if err != nil {
+ return err
+ }
+
+ // Find node
+ if nodePath == "" {
+ return fmt.Errorf("cannot find path to node. Specify with --node")
+ }
+
+ for _, file := range []string{"cts.js", "dawn.node"} {
+ if !fileutils.IsFile(filepath.Join(binPath, file)) {
+ return fmt.Errorf("'%v' does not contain '%v'", binPath, file)
+ }
+ }
+
+ ctsJS := filepath.Join(binPath, "cts.js")
+
+ ctx := context.Background()
+ if debugger == "" {
+ timeoutCtx, cancel := context.WithTimeout(context.Background(), common.TestCaseTimeout)
+ defer cancel()
+ ctx = timeoutCtx
+ }
+
+ quotedFlags := make([]string, len(flags))
+ for i, f := range flags {
+ quotedFlags[i] = fmt.Sprintf("'%v'", f)
+ }
+
+ args = []string{
+ "-e",
+ fmt.Sprintf("const { create } = require('%v'); navigator = { gpu: create([%v]) }; require('%v')", ctsJS, strings.Join(quotedFlags, ", "), scriptPath),
+ }
+
+ exe := nodePath
+
+ if debugger != "" {
+ args = append([]string{"--", exe}, args...)
+ exe, err = exec.LookPath(debugger)
+ if err != nil {
+ return err
+ }
+ }
+
+ cmd := exec.CommandContext(ctx, exe, args...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
diff --git a/tools/src/cmd/run-cts/node/cmd.go b/tools/src/cmd/run-cts/node/cmd.go
index cd7d35a..4edea34 100644
--- a/tools/src/cmd/run-cts/node/cmd.go
+++ b/tools/src/cmd/run-cts/node/cmd.go
@@ -35,64 +35,14 @@
"os/exec"
"path/filepath"
"runtime"
- "strings"
"dawn.googlesource.com/dawn/tools/src/cmd/run-cts/common"
"dawn.googlesource.com/dawn/tools/src/cov"
+ "dawn.googlesource.com/dawn/tools/src/dawn/node"
"dawn.googlesource.com/dawn/tools/src/fileutils"
"dawn.googlesource.com/dawn/tools/src/subcmd"
)
-type dawnFlags []string
-
-func (f *dawnFlags) String() string {
- return strings.Join(*f, "")
-}
-
-func (f *dawnFlags) Set(value string) error {
- // Multiple flags must be passed in individually:
- // -flag=a=b -dawn_node_flag=c=d
- *f = append(*f, value)
- return nil
-}
-
-// Consolidates all the delimiter separated flags with a given prefix into a single flag.
-// Example:
-// Given the flags: ["foo=a", "bar", "foo=b,c"]
-// GlobListFlags("foo=", ",") will transform the flags to: ["bar", "foo=a,b,c"]
-func (f *dawnFlags) GlobListFlags(prefix string, delimiter string) {
- list := []string{}
- i := 0
- for _, flag := range *f {
- if strings.HasPrefix(flag, prefix) {
- // Trim the prefix.
- value := flag[len(prefix):]
- // Extract the deliminated values.
- list = append(list, strings.Split(value, delimiter)...)
- } else {
- (*f)[i] = flag
- i++
- }
- }
- (*f) = (*f)[:i]
- if len(list) > 0 {
- // Append back the consolidated flags.
- f.Set(prefix + strings.Join(list, delimiter))
- }
-}
-
-// defaultBinPath looks for the binary output directory at <dawn>/out/active.
-// This is used as the default for the --bin command line flag.
-func defaultBinPath() string {
- if dawnRoot := fileutils.DawnRoot(); dawnRoot != "" {
- bin := filepath.Join(dawnRoot, "out/active")
- if info, err := os.Stat(bin); err == nil && info.IsDir() {
- return bin
- }
- }
- return ""
-}
-
type flags struct {
common.Flags
bin string
@@ -108,7 +58,7 @@
genCoverage bool
compatibilityMode bool
skipVSCodeInfo bool
- dawn dawnFlags
+ dawn node.Flags
}
func init() {
@@ -141,7 +91,7 @@
}
c.flags.Flags.Register()
- flag.StringVar(&c.flags.bin, "bin", defaultBinPath(), "path to the directory holding cts.js and dawn.node")
+ flag.StringVar(&c.flags.bin, "bin", fileutils.BuildPath(), "path to the directory holding cts.js and dawn.node")
flag.BoolVar(&c.flags.isolated, "isolate", false, "run each test in an isolated process")
flag.BoolVar(&c.flags.build, "build", true, "attempt to build the CTS before running")
flag.BoolVar(&c.flags.validate, "validate", false, "enable backend validation")
@@ -245,31 +195,19 @@
return fmt.Errorf("only a single query can be provided")
}
- // For Windows, set the DLL directory to bin so that Dawn loads dxcompiler.dll from there.
- c.flags.dawn.Set("dlldir=" + c.flags.bin)
+ c.flags.dawn.SetOptions(node.Options{
+ BinDir: c.flags.bin,
+ Backend: c.flags.backend,
+ Adapter: c.flags.adapterName,
+ Validate: c.flags.validate,
+ AllowUnsafeAPIs: true,
+ DumpShaders: c.flags.dumpShaders,
+ UseFXC: c.flags.fxc,
+ })
- // Forward the backend and adapter to use, if specified.
- if c.flags.backend != "default" {
- fmt.Println("Forcing backend to", c.flags.backend)
- c.flags.dawn.Set("backend=" + c.flags.backend)
- }
- if c.flags.adapterName != "" {
- c.flags.dawn.Set("adapter=" + c.flags.adapterName)
- }
- if c.flags.validate {
- c.flags.dawn.Set("validate=1")
- }
-
- // While running the CTS, always allow unsafe APIs so they can be tested.
- c.flags.dawn.Set("enable-dawn-features=allow_unsafe_apis")
if c.flags.dumpShaders {
c.flags.Verbose = true
- c.flags.dawn.Set("enable-dawn-features=dump_shaders,disable_symbol_renaming")
}
- if c.flags.fxc {
- c.flags.dawn.Set("disable-dawn-features=use_dxc")
- }
- c.flags.dawn.GlobListFlags("enable-dawn-features=", ",")
state, err := c.flags.Process()
if err != nil {
diff --git a/tools/src/dawn/node/node.go b/tools/src/dawn/node/node.go
new file mode 100644
index 0000000..fbacee1
--- /dev/null
+++ b/tools/src/dawn/node/node.go
@@ -0,0 +1,115 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Package node holds common code for running dawn/node
+package node
+
+import (
+ "fmt"
+ "strings"
+)
+
+// Flags for running dawn/node
+type Flags []string
+
+func (f *Flags) String() string {
+ return strings.Join(*f, "")
+}
+
+// Set sets a dawn flag
+func (f *Flags) Set(value string) error {
+ // Multiple flags must be passed in individually:
+ // -flag=a=b -dawn_node_flag=c=d
+ *f = append(*f, value)
+ return nil
+}
+
+// Consolidates all the delimiter separated flags with a given prefix into a single flag.
+// Example:
+// Given the flags: ["foo=a", "bar", "foo=b,c"]
+// GlobListFlags("foo=", ",") will transform the flags to: ["bar", "foo=a,b,c"]
+func (f *Flags) GlobListFlags(prefix string, delimiter string) {
+ list := []string{}
+ i := 0
+ for _, flag := range *f {
+ if strings.HasPrefix(flag, prefix) {
+ // Trim the prefix.
+ value := flag[len(prefix):]
+ // Extract the deliminated values.
+ list = append(list, strings.Split(value, delimiter)...)
+ } else {
+ (*f)[i] = flag
+ i++
+ }
+ }
+ (*f) = (*f)[:i]
+ if len(list) > 0 {
+ // Append back the consolidated flags.
+ f.Set(prefix + strings.Join(list, delimiter))
+ }
+}
+
+// Options that can be passed to Flags.SetOptions
+type Options struct {
+ BinDir string
+ Backend string
+ Adapter string
+ Validate bool
+ AllowUnsafeAPIs bool
+ DumpShaders bool
+ UseFXC bool
+}
+
+func (f *Flags) SetOptions(opts Options) error {
+ // For Windows, set the DLL directory to bin so that Dawn loads dxcompiler.dll from there.
+ f.Set("dlldir=" + opts.BinDir)
+
+ // Forward the backend and adapter to use, if specified.
+ if opts.Backend != "" && opts.Backend != "default" {
+ fmt.Println("Forcing backend to", opts.Backend)
+ f.Set("backend=" + opts.Backend)
+ }
+ if opts.Adapter != "" {
+ f.Set("adapter=" + opts.Adapter)
+ }
+ if opts.Validate {
+ f.Set("validate=1")
+ }
+
+ if opts.AllowUnsafeAPIs {
+ f.Set("enable-dawn-features=allow_unsafe_apis")
+ }
+ if opts.DumpShaders {
+ f.Set("enable-dawn-features=dump_shaders,disable_symbol_renaming")
+ }
+ if opts.UseFXC {
+ f.Set("disable-dawn-features=use_dxc")
+ }
+ f.GlobListFlags("enable-dawn-features=", ",")
+
+ return nil
+}
diff --git a/tools/src/fileutils/paths.go b/tools/src/fileutils/paths.go
index a2edcfb..5070b56 100644
--- a/tools/src/fileutils/paths.go
+++ b/tools/src/fileutils/paths.go
@@ -120,6 +120,18 @@
return ""
}
+// BuildPath looks for the binary output directory at '<dawn>/out/active'.
+// Returns the path if found, otherwise an empty string.
+func BuildPath() string {
+ if dawnRoot := DawnRoot(); dawnRoot != "" {
+ bin := filepath.Join(dawnRoot, "out/active")
+ if info, err := os.Stat(bin); err == nil && info.IsDir() {
+ return bin
+ }
+ }
+ return ""
+}
+
// IsDir returns true if the path resolves to a directory
func IsDir(path string) bool {
s, err := os.Stat(path)