|  | // 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 | 
|  | 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 { | 
|  | symlink  bool | 
|  | buildDir string | 
|  | npmPath  string | 
|  | } | 
|  | } | 
|  |  | 
|  | func (Cmd) Name() string { | 
|  | return "install" | 
|  | } | 
|  |  | 
|  | func (Cmd) Desc() string { | 
|  | return `install installs the tintd vscode extension for local development` | 
|  | } | 
|  |  | 
|  | func (c *Cmd) RegisterFlags(ctx context.Context, _ any) ([]string, error) { | 
|  | dawnRoot := fileutils.DawnRoot() | 
|  | npmPath, _ := exec.LookPath("npm") | 
|  | flag.BoolVar(&c.flags.symlink, "symlink", false, "create a symlink from the vscode extension directory to the build directory") | 
|  | 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) | 
|  | } | 
|  |  | 
|  | 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 c.flags.symlink { | 
|  | // Symlink the vscode extensions directory to the build directory | 
|  | if err := os.Symlink(pkgDir, vscodeTintdDir); err != nil { | 
|  | return fmt.Errorf("failed to create symlink '%v' <- '%v': %w", pkgDir, vscodeTintdDir, err) | 
|  | } | 
|  | } else { | 
|  | // Copy the build directory to vscode extensions directory | 
|  | if err := fileutils.CopyDir(vscodeTintdDir, pkgDir); err != nil { | 
|  | return fmt.Errorf("failed to copy '%v' to '%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 | 
|  | } |