mirror of
				https://github.com/taigrr/wasm-experiments
				synced 2025-01-18 04:03:21 -08:00 
			
		
		
		
	
		
			
				
	
	
		
			343 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			343 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package deployer
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"crypto/sha1"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"io/ioutil"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	"os/exec"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 	"text/template"
 | |
| 
 | |
| 	"github.com/dave/jsgo/assets/std"
 | |
| 	"github.com/dave/jsgo/server/servermsg"
 | |
| 	"github.com/dave/jsgo/server/wasm/messages"
 | |
| 	"github.com/dave/services/constor/constormsg"
 | |
| 	"github.com/dave/wasmgo/cmd/cmdconfig"
 | |
| 	"github.com/dave/wasmgo/config"
 | |
| 	"github.com/gorilla/websocket"
 | |
| 	"github.com/pkg/browser"
 | |
| )
 | |
| 
 | |
| const CLIENT_VERSION = "1.0.0"
 | |
| 
 | |
| func Start(cfg *cmdconfig.Config) error {
 | |
| 
 | |
| 	var debug io.Writer
 | |
| 	if cfg.Verbose {
 | |
| 		debug = os.Stdout
 | |
| 	} else {
 | |
| 		debug = ioutil.Discard
 | |
| 	}
 | |
| 
 | |
| 	// create a temp dir
 | |
| 	tempDir, err := ioutil.TempDir("", "")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer os.RemoveAll(tempDir)
 | |
| 
 | |
| 	fpath := filepath.Join(tempDir, "out.wasm")
 | |
| 
 | |
| 	sourceDir, err := runGoList(cfg)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if err := runGoBuild(cfg, fpath, debug); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	binaryBytes, err := ioutil.ReadFile(fpath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	binarySha := sha1.New()
 | |
| 	if _, err := io.Copy(binarySha, bytes.NewBuffer(binaryBytes)); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	files := map[messages.DeployFileType]messages.DeployFile{}
 | |
| 
 | |
| 	files[messages.DeployFileTypeWasm] = messages.DeployFile{
 | |
| 		DeployFileKey: messages.DeployFileKey{
 | |
| 			Type: messages.DeployFileTypeWasm,
 | |
| 			Hash: fmt.Sprintf("%x", binarySha.Sum(nil)),
 | |
| 		},
 | |
| 		Contents: binaryBytes,
 | |
| 	}
 | |
| 
 | |
| 	loaderBuf := &bytes.Buffer{}
 | |
| 	loaderSha := sha1.New()
 | |
| 	wasmUrl := fmt.Sprintf("%s://%s/%x.wasm", config.Protocol[config.Pkg], config.Host[config.Pkg], binarySha.Sum(nil))
 | |
| 	loaderVars := struct{ Binary string }{
 | |
| 		Binary: wasmUrl,
 | |
| 	}
 | |
| 	if err := loaderTemplateMin.Execute(io.MultiWriter(loaderBuf, loaderSha), loaderVars); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	files[messages.DeployFileTypeLoader] = messages.DeployFile{
 | |
| 		DeployFileKey: messages.DeployFileKey{
 | |
| 			Type: messages.DeployFileTypeLoader,
 | |
| 			Hash: fmt.Sprintf("%x", loaderSha.Sum(nil)),
 | |
| 		},
 | |
| 		Contents: loaderBuf.Bytes(),
 | |
| 	}
 | |
| 
 | |
| 	indexBuf := &bytes.Buffer{}
 | |
| 	indexSha := sha1.New()
 | |
| 	loaderUrl := fmt.Sprintf("%s://%s/%x.js", config.Protocol[config.Pkg], config.Host[config.Pkg], loaderSha.Sum(nil))
 | |
| 	indexVars := struct{ Script, Loader string }{
 | |
| 		Script: fmt.Sprintf("%s://%s/wasm_exec.%s.js", config.Protocol[config.Pkg], config.Host[config.Pkg], std.Wasm[true]),
 | |
| 		Loader: loaderUrl,
 | |
| 	}
 | |
| 	indexTemplate := defaultIndexTemplate
 | |
| 	if cfg.Index != "" {
 | |
| 		indexFilename := cfg.Index
 | |
| 		if cfg.Path != "" {
 | |
| 			indexFilename = filepath.Join(sourceDir, cfg.Index)
 | |
| 		}
 | |
| 		indexTemplateBytes, err := ioutil.ReadFile(indexFilename)
 | |
| 		if err != nil && !os.IsNotExist(err) {
 | |
| 			return err
 | |
| 		}
 | |
| 		if err == nil {
 | |
| 			indexTemplate, err = template.New("main").Parse(string(indexTemplateBytes))
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if err := indexTemplate.Execute(io.MultiWriter(indexBuf, indexSha), indexVars); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	files[messages.DeployFileTypeIndex] = messages.DeployFile{
 | |
| 		DeployFileKey: messages.DeployFileKey{
 | |
| 			Type: messages.DeployFileTypeIndex,
 | |
| 			Hash: fmt.Sprintf("%x", indexSha.Sum(nil)),
 | |
| 		},
 | |
| 		Contents: indexBuf.Bytes(),
 | |
| 	}
 | |
| 
 | |
| 	indexUrl := fmt.Sprintf("%s://%s/%x", config.Protocol[config.Index], config.Host[config.Index], indexSha.Sum(nil))
 | |
| 
 | |
| 	message := messages.DeployQuery{
 | |
| 		Version: CLIENT_VERSION,
 | |
| 		Files: []messages.DeployFileKey{
 | |
| 			files[messages.DeployFileTypeWasm].DeployFileKey,
 | |
| 			files[messages.DeployFileTypeIndex].DeployFileKey,
 | |
| 			files[messages.DeployFileTypeLoader].DeployFileKey,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	fmt.Fprintln(debug, "Querying server...")
 | |
| 
 | |
| 	protocol := "wss"
 | |
| 	if config.Protocol[config.Wasm] == "http" {
 | |
| 		protocol = "ws"
 | |
| 	}
 | |
| 	conn, _, err := websocket.DefaultDialer.Dial(
 | |
| 		fmt.Sprintf("%s://%s/_wasm/", protocol, config.Host[config.Wasm]),
 | |
| 		http.Header{"Origin": []string{fmt.Sprintf("%s://%s/", config.Protocol[config.Wasm], config.Host[config.Wasm])}},
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	messageBytes, messageType, err := messages.Marshal(message)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err := conn.WriteMessage(messageType, messageBytes); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	var response messages.DeployQueryResponse
 | |
| 	var done bool
 | |
| 	for !done {
 | |
| 		_, replyBytes, err := conn.ReadMessage()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		message, err := messages.Unmarshal(replyBytes)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		switch message := message.(type) {
 | |
| 		case messages.DeployQueryResponse:
 | |
| 			response = message
 | |
| 			done = true
 | |
| 		case servermsg.Queueing:
 | |
| 			// don't print
 | |
| 		case servermsg.Error:
 | |
| 			return errors.New(message.Message)
 | |
| 		case messages.DeployClientVersionNotSupported:
 | |
| 			return errors.New("this client version is not supported - try `go get -u github.com/dave/wasmgo`")
 | |
| 		default:
 | |
| 			// unexpected
 | |
| 			fmt.Fprintf(debug, "Unexpected message from server: %#v\n", message)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(response.Required) > 0 {
 | |
| 
 | |
| 		fmt.Fprintf(debug, "Files required: %d.\n", len(response.Required))
 | |
| 		fmt.Fprintln(debug, "Bundling required files...")
 | |
| 
 | |
| 		var required []messages.DeployFile
 | |
| 		for _, k := range response.Required {
 | |
| 			file := files[k.Type]
 | |
| 			if file.Hash == "" {
 | |
| 				return errors.New("server requested file not found")
 | |
| 			}
 | |
| 			required = append(required, file)
 | |
| 		}
 | |
| 
 | |
| 		payload := messages.DeployPayload{Files: required}
 | |
| 		payloadBytes, payloadType, err := messages.Marshal(payload)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		if err := conn.WriteMessage(payloadType, payloadBytes); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		fmt.Fprintf(debug, "Sending payload: %dKB.\n", len(payloadBytes)/1024)
 | |
| 
 | |
| 		done = false
 | |
| 		for !done {
 | |
| 			_, replyBytes, err := conn.ReadMessage()
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			message, err := messages.Unmarshal(replyBytes)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			switch message := message.(type) {
 | |
| 			case messages.DeployDone:
 | |
| 				done = true
 | |
| 			case servermsg.Queueing:
 | |
| 				// don't print
 | |
| 			case servermsg.Error:
 | |
| 				return errors.New(message.Message)
 | |
| 			case constormsg.Storing:
 | |
| 				if message.Remain > 0 || message.Finished > 0 {
 | |
| 					fmt.Fprintf(debug, "Storing, %d to go.\n", message.Remain)
 | |
| 				}
 | |
| 			default:
 | |
| 				// unexpected
 | |
| 				fmt.Fprintf(debug, "Unexpected message from server: %#v\n", message)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		fmt.Fprintln(debug, "Sending done.")
 | |
| 
 | |
| 	} else {
 | |
| 		fmt.Fprintln(debug, "No files required.")
 | |
| 	}
 | |
| 
 | |
| 	outputVars := struct {
 | |
| 		Page   string
 | |
| 		Loader string
 | |
| 	}{
 | |
| 		Page:   indexUrl,
 | |
| 		Loader: loaderUrl,
 | |
| 	}
 | |
| 
 | |
| 	if cfg.Json {
 | |
| 		out, err := json.Marshal(outputVars)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		fmt.Println(string(out))
 | |
| 	} else {
 | |
| 		tpl, err := template.New("main").Parse(cfg.Template)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		tpl.Execute(os.Stdout, outputVars)
 | |
| 		fmt.Println("")
 | |
| 	}
 | |
| 
 | |
| 	if cfg.Open {
 | |
| 		browser.OpenURL(indexUrl)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func runGoBuild(cfg *cmdconfig.Config, fpath string, debug io.Writer) error {
 | |
| 	args := []string{"build", "-o", fpath}
 | |
| 
 | |
| 	extraFlags := strings.Fields(cfg.Flags)
 | |
| 	for _, f := range extraFlags {
 | |
| 		args = append(args, f)
 | |
| 	}
 | |
| 
 | |
| 	if cfg.BuildTags != "" {
 | |
| 		args = append(args, "-tags", cfg.BuildTags)
 | |
| 	}
 | |
| 
 | |
| 	path := "."
 | |
| 	if cfg.Path != "" {
 | |
| 		path = cfg.Path
 | |
| 	}
 | |
| 	args = append(args, path)
 | |
| 
 | |
| 	fmt.Fprintln(debug, "Compiling...")
 | |
| 
 | |
| 	cmd := exec.Command(cfg.Command, args...)
 | |
| 	cmd.Env = os.Environ()
 | |
| 	cmd.Env = append(cmd.Env, "GOARCH=wasm")
 | |
| 	cmd.Env = append(cmd.Env, "GOOS=js")
 | |
| 	output, err := cmd.CombinedOutput()
 | |
| 	if err != nil {
 | |
| 		if strings.Contains(string(output), "unsupported GOOS/GOARCH pair js/wasm") {
 | |
| 			return errors.New("you need Go v1.11 to compile WASM. It looks like your default `go` command is not v1.11. Perhaps you need the -c flag to specify a custom command name - e.g. `-c=go1.11beta3`")
 | |
| 		}
 | |
| 		return fmt.Errorf("%v: %s", err, string(output))
 | |
| 	}
 | |
| 	if len(output) > 0 {
 | |
| 		return fmt.Errorf("%s", string(output))
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func runGoList(cfg *cmdconfig.Config) (string, error) {
 | |
| 	args := []string{"list"}
 | |
| 
 | |
| 	if cfg.BuildTags != "" {
 | |
| 		args = append(args, "-tags", cfg.BuildTags)
 | |
| 	}
 | |
| 
 | |
| 	args = append(args, "-f", "{{.Dir}}")
 | |
| 
 | |
| 	path := "."
 | |
| 	if cfg.Path != "" {
 | |
| 		path = cfg.Path
 | |
| 	}
 | |
| 	args = append(args, path)
 | |
| 
 | |
| 	cmd := exec.Command(cfg.Command, args...)
 | |
| 	cmd.Env = os.Environ()
 | |
| 	cmd.Env = append(cmd.Env, "GOARCH=wasm")
 | |
| 	cmd.Env = append(cmd.Env, "GOOS=js")
 | |
| 	output, err := cmd.CombinedOutput()
 | |
| 	if err != nil {
 | |
| 		if strings.Contains(string(output), "unsupported GOOS/GOARCH pair js/wasm") {
 | |
| 			return "", errors.New("you need Go v1.11 to compile WASM. It looks like your default `go` command is not v1.11. Perhaps you need the -c flag to specify a custom command name - e.g. `-c=go1.11beta3`")
 | |
| 		}
 | |
| 		return "", fmt.Errorf("%v: %s", err, string(output))
 | |
| 	}
 | |
| 	return string(output), nil
 | |
| }
 |