[tint][tools] Add a 'tintd install' command
Installs the tintd vscode extension for local development via a symlink to the build directory
Bug: tint:2127
Change-Id: If813eebd58d9eb2fbc2a3f41a69174d88d263fb9
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/179100
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/tools/src/cmd/tintd/common/cmds.go b/tools/src/cmd/tintd/common/cmds.go
new file mode 100644
index 0000000..44edcd5
--- /dev/null
+++ b/tools/src/cmd/tintd/common/cmds.go
@@ -0,0 +1,44 @@
+// 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 common
+
+import (
+ "dawn.googlesource.com/dawn/tools/src/subcmd"
+)
+
+// The registered commands
+var commands []Command
+
+// Command is the type of a single cts command
+type Command = subcmd.Command[any]
+
+// Register registers the command for use by the 'cts' tool
+func Register(c Command) { commands = append(commands, c) }
+
+// Commands returns all the commands registered
+func Commands() []Command { return commands }
diff --git a/tools/src/cmd/tintd/install/install.go b/tools/src/cmd/tintd/install/install.go
new file mode 100644
index 0000000..fadf860
--- /dev/null
+++ b/tools/src/cmd/tintd/install/install.go
@@ -0,0 +1,144 @@
+// 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 install installs the tintd vscode extension for local development
+// via a symlink to the build directory
+package install
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "dawn.googlesource.com/dawn/tools/src/cmd/tintd/common"
+ "dawn.googlesource.com/dawn/tools/src/fileutils"
+)
+
+func init() {
+ common.Register(&Cmd{})
+}
+
+type Cmd struct {
+ flags struct {
+ buildDir string
+ npmPath string
+ }
+}
+
+func (Cmd) Name() string {
+ return "install"
+}
+
+func (Cmd) Desc() string {
+ return `install installs the tintd vscode extension for local development via a symlink to the build directory`
+}
+
+func (c *Cmd) RegisterFlags(ctx context.Context, _ any) ([]string, error) {
+ dawnRoot := fileutils.DawnRoot()
+ npmPath, _ := exec.LookPath("npm")
+ flag.StringVar(&c.flags.buildDir, "build", filepath.Join(dawnRoot, "out", "active"), "the output build directory")
+ flag.StringVar(&c.flags.npmPath, "npm", npmPath, "path to npm")
+
+ return nil, nil
+}
+
+func (c Cmd) Run(ctx context.Context, _ any) error {
+ pkgDir := c.findPackage()
+ if pkgDir == "" {
+ return fmt.Errorf("could not find extension package directory at '%v'", c.flags.buildDir)
+ }
+
+ if !fileutils.IsExe(c.flags.npmPath) {
+ return fmt.Errorf("could not find npm")
+ }
+
+ // Build the package
+ npmCmd := exec.Command(c.flags.npmPath, "install")
+ npmCmd.Dir = pkgDir
+ if out, err := npmCmd.CombinedOutput(); err != nil {
+ return fmt.Errorf("npm install failed:\n%v\n%v", err, string(out))
+ }
+
+ // Load the package to get the name and version
+ pkg := struct {
+ Name string
+ Version string
+ }{}
+ packageJSONPath := filepath.Join(pkgDir, "package.json")
+ packageJSON, err := os.ReadFile(packageJSONPath)
+ if err != nil {
+ return fmt.Errorf("could not open '%v'", packageJSONPath)
+ }
+ if err := json.NewDecoder(bytes.NewReader(packageJSON)).Decode(&pkg); err != nil {
+ return fmt.Errorf("could not parse '%v': %v", packageJSONPath, err)
+ }
+
+ // Symlink to vscode extensions directory
+ home, err := os.UserHomeDir()
+ if err != nil {
+ return fmt.Errorf("failed to obtain home directory: %w", err)
+ }
+ vscodeBaseExtsDir := filepath.Join(home, ".vscode", "extensions")
+ if !fileutils.IsDir(vscodeBaseExtsDir) {
+ return fmt.Errorf("vscode extensions directory not found at '%v'", vscodeBaseExtsDir)
+ }
+
+ vscodeTintdDir := filepath.Join(vscodeBaseExtsDir, fmt.Sprintf("google.%v-%v", pkg.Name, pkg.Version))
+ os.RemoveAll(vscodeTintdDir)
+
+ if err := os.Symlink(pkgDir, vscodeTintdDir); err != nil {
+ return fmt.Errorf("failed to create symlink '%v' <- '%v': %w", pkgDir, vscodeTintdDir, err)
+ }
+
+ return nil
+}
+
+// findPackage looks for and returns the tintd package directory. Returns an empty string if not found.
+func (c Cmd) findPackage() string {
+ searchPaths := []string{
+ filepath.Join(c.flags.buildDir, "gen/vscode"),
+ c.flags.buildDir,
+ }
+ files := []string{"tintd", "package.json"}
+
+nextDir:
+ for _, dir := range searchPaths {
+ for _, file := range files {
+ if !fileutils.IsFile(filepath.Join(dir, file)) {
+ continue nextDir
+ }
+ }
+ return dir
+ }
+
+ return "" // Not found
+}
diff --git a/tools/src/cmd/tintd/main.go b/tools/src/cmd/tintd/main.go
new file mode 100644
index 0000000..55fc9e9
--- /dev/null
+++ b/tools/src/cmd/tintd/main.go
@@ -0,0 +1,55 @@
+// 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.
+
+// tintd is a collection of sub-commands for the tintd language server
+//
+// To view available commands run: '<dawn>/tools/run tintd --help'
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "dawn.googlesource.com/dawn/tools/src/cmd/tintd/common"
+ "dawn.googlesource.com/dawn/tools/src/subcmd"
+
+ // Register sub-commands
+ _ "dawn.googlesource.com/dawn/tools/src/cmd/tintd/install"
+)
+
+func main() {
+ ctx := context.Background()
+
+ var noArg any
+ if err := subcmd.Run(ctx, noArg, common.Commands()...); err != nil {
+ if err != subcmd.ErrInvalidCLA {
+ fmt.Fprintln(os.Stderr, err)
+ }
+ os.Exit(1)
+ }
+}