dawn_node: Only rebuild CTS if something has changed
https://github.com/gpuweb/cts/pull/788 significantly improves the performance of incremental builds, but it can still take around 2 seconds to do that incremental build. Have the run-cts tool scan the CTS source tree for the most recent modification, and only rebuild if the timestamp is more recent than the last build time. Significantly reduces startup overhead of the tool. Change-Id: I7e9227127c02e6d4dc8875a577178956bf5e2481 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/66822 Reviewed-by: Antonio Maiorano <amaiorano@google.com> Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
parent
1bf932e7b4
commit
06b827ee92
|
@ -17,6 +17,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -150,21 +151,41 @@ func run() error {
|
||||||
r.log = newLogger(writer)
|
r.log = newLogger(writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if build {
|
cache := cache{}
|
||||||
// Attempt to build the CTS (instead of using the `setup-ts-in-node` transpiler)
|
cachePath := dawnNode + ".runcts.cache"
|
||||||
if npx != "" {
|
if err := cache.load(cachePath); err != nil && verbose {
|
||||||
if err := r.buildCTS(); err != nil {
|
fmt.Println("failed to load cache from", cachePath, err)
|
||||||
return fmt.Errorf("failed to build CTS: %w", err)
|
|
||||||
} else {
|
|
||||||
r.evalScript = `require('./out-node/common/runtime/cmdline.js');`
|
|
||||||
}
|
}
|
||||||
|
defer cache.save(cachePath)
|
||||||
|
|
||||||
|
// Scan the CTS source to determine the most recent change to the CTS source
|
||||||
|
mostRecentSourceChange, err := r.scanSourceTimestamps(verbose)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to scan source files for modified timestamps: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctsNeedsRebuild := mostRecentSourceChange.After(cache.BuildTimestamp)
|
||||||
|
if build {
|
||||||
|
if verbose {
|
||||||
|
fmt.Println("CTS needs rebuild:", ctsNeedsRebuild)
|
||||||
|
}
|
||||||
|
|
||||||
|
if npx != "" {
|
||||||
|
if ctsNeedsRebuild {
|
||||||
|
if err := r.buildCTS(verbose); err != nil {
|
||||||
|
return fmt.Errorf("failed to build CTS: %w", err)
|
||||||
|
}
|
||||||
|
cache.BuildTimestamp = mostRecentSourceChange
|
||||||
|
}
|
||||||
|
// Use the prebuilt CTS (instead of using the `setup-ts-in-node` transpiler)
|
||||||
|
r.evalScript = `require('./out-node/common/runtime/cmdline.js');`
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("npx not found on PATH. Using runtime TypeScript transpilation (slow)")
|
fmt.Println("npx not found on PATH. Using runtime TypeScript transpilation (slow)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all the test cases that match the given queries.
|
// Find all the test cases that match the given queries.
|
||||||
if err := r.gatherTestCases(queries); err != nil {
|
if err := r.gatherTestCases(queries, verbose); err != nil {
|
||||||
return fmt.Errorf("failed to gather test cases: %w", err)
|
return fmt.Errorf("failed to gather test cases: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,6 +222,31 @@ func (l *logger) logResults(res result) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache holds cached information between runs to optimize runs
|
||||||
|
type cache struct {
|
||||||
|
BuildTimestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// load loads the cache information from the JSON file at path
|
||||||
|
func (c *cache) load(path string) error {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return json.NewDecoder(f).Decode(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// save saves the cache information to the JSON file at path
|
||||||
|
func (c *cache) save(path string) error {
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return json.NewEncoder(f).Encode(c)
|
||||||
|
}
|
||||||
|
|
||||||
type runner struct {
|
type runner struct {
|
||||||
numRunners int
|
numRunners int
|
||||||
verbose bool
|
verbose bool
|
||||||
|
@ -210,10 +256,47 @@ type runner struct {
|
||||||
log logger
|
log logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// scanSourceTimestamps scans all the .js and .ts files in all subdirectories of
|
||||||
|
// r.cts, and returns the file with the most recent timestamp.
|
||||||
|
func (r *runner) scanSourceTimestamps(verbose bool) (time.Time, error) {
|
||||||
|
if verbose {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Println("Scanning .js / .ts files for changes...")
|
||||||
|
defer func() {
|
||||||
|
fmt.Println("completed in", time.Since(start))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Join(r.cts, "src")
|
||||||
|
|
||||||
|
mostRecentChange := time.Time{}
|
||||||
|
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
switch filepath.Ext(path) {
|
||||||
|
case ".ts", ".js":
|
||||||
|
if info.ModTime().After(mostRecentChange) {
|
||||||
|
mostRecentChange = info.ModTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
return mostRecentChange, nil
|
||||||
|
}
|
||||||
|
|
||||||
// buildCTS calls `npx grunt run:build-out-node` in the CTS directory to compile
|
// buildCTS calls `npx grunt run:build-out-node` in the CTS directory to compile
|
||||||
// the TypeScript files down to JavaScript. Doing this once ahead of time can be
|
// the TypeScript files down to JavaScript. Doing this once ahead of time can be
|
||||||
// much faster than dynamically transpiling when there are many tests to run.
|
// much faster than dynamically transpiling when there are many tests to run.
|
||||||
func (r *runner) buildCTS() error {
|
func (r *runner) buildCTS(verbose bool) error {
|
||||||
|
if verbose {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Println("Building CTS...")
|
||||||
|
defer func() {
|
||||||
|
fmt.Println("completed in", time.Since(start))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.Command(r.npx, "grunt", "run:build-out-node")
|
cmd := exec.Command(r.npx, "grunt", "run:build-out-node")
|
||||||
cmd.Dir = r.cts
|
cmd.Dir = r.cts
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
|
@ -225,7 +308,15 @@ func (r *runner) buildCTS() error {
|
||||||
|
|
||||||
// gatherTestCases() queries the CTS for all test cases that match the given
|
// gatherTestCases() queries the CTS for all test cases that match the given
|
||||||
// query. On success, gatherTestCases() populates r.testcases.
|
// query. On success, gatherTestCases() populates r.testcases.
|
||||||
func (r *runner) gatherTestCases(queries []string) error {
|
func (r *runner) gatherTestCases(queries []string, verbose bool) error {
|
||||||
|
if verbose {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Println("Gathering test cases...")
|
||||||
|
defer func() {
|
||||||
|
fmt.Println("completed in", time.Since(start))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
args := append([]string{
|
args := append([]string{
|
||||||
"-e", r.evalScript,
|
"-e", r.evalScript,
|
||||||
"--", // Start of arguments
|
"--", // Start of arguments
|
||||||
|
|
Loading…
Reference in New Issue