178 lines
4.1 KiB
Go
178 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func main() {
|
|
var frontendDev string
|
|
var frontendURL string
|
|
var readyTimeout time.Duration
|
|
var watchTarget string
|
|
var workspaceDir string
|
|
|
|
flag.StringVar(&frontendDev, "frontend-dev", "", "")
|
|
flag.StringVar(&frontendURL, "frontend-url", "http://127.0.0.1:9245", "")
|
|
flag.DurationVar(&readyTimeout, "ready-timeout", 2*time.Minute, "")
|
|
flag.StringVar(&watchTarget, "watch-target", "", "")
|
|
flag.StringVar(&workspaceDir, "workspace-dir", ".", "")
|
|
flag.Parse()
|
|
|
|
require(frontendDev != "", "missing --frontend-dev")
|
|
require(watchTarget != "", "missing --watch-target")
|
|
|
|
workspaceRoot := os.Getenv("BUILD_WORKSPACE_DIRECTORY")
|
|
if workspaceRoot == "" {
|
|
workspaceRoot = "."
|
|
}
|
|
runfilesDir := resolveRunfilesDir()
|
|
|
|
frontendCommand := exec.Command(frontendDev)
|
|
frontendCommand.Dir = workspaceRoot
|
|
frontendCommand.Env = withRunfilesEnv(os.Environ(), runfilesDir)
|
|
frontendCommand.Stdout = os.Stdout
|
|
frontendCommand.Stderr = os.Stderr
|
|
|
|
must(frontendCommand.Start())
|
|
waitCh := make(chan error, 1)
|
|
go func() {
|
|
waitCh <- frontendCommand.Wait()
|
|
}()
|
|
defer terminate(frontendCommand, waitCh)
|
|
|
|
must(waitForURL(frontendURL, readyTimeout, waitCh))
|
|
|
|
watchCommand, err := resolveWatchCommand(watchTarget)
|
|
must(err)
|
|
watchCommand.Dir = workspaceRoot
|
|
watchCommand.Env = os.Environ()
|
|
watchCommand.Stdout = os.Stdout
|
|
watchCommand.Stderr = os.Stderr
|
|
watchCommand.Stdin = os.Stdin
|
|
|
|
if err := watchCommand.Run(); err != nil {
|
|
if exitError, ok := err.(*exec.ExitError); ok {
|
|
os.Exit(exitError.ExitCode())
|
|
}
|
|
panic(err)
|
|
}
|
|
|
|
_ = workspaceDir
|
|
}
|
|
|
|
func resolveRunfilesDir() string {
|
|
for _, candidate := range []string{
|
|
os.Getenv("RUNFILES_DIR"),
|
|
os.Getenv("RUNFILES"),
|
|
} {
|
|
if candidate != "" {
|
|
return candidate
|
|
}
|
|
}
|
|
|
|
executablePath, err := os.Executable()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
normalizedPath := filepath.Clean(executablePath)
|
|
marker := ".runfiles"
|
|
if index := strings.Index(normalizedPath, marker+string(os.PathSeparator)); index >= 0 {
|
|
return normalizedPath[:index+len(marker)]
|
|
}
|
|
if strings.HasSuffix(normalizedPath, marker) {
|
|
return normalizedPath
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func withRunfilesEnv(environment []string, runfilesDir string) []string {
|
|
if runfilesDir == "" {
|
|
return environment
|
|
}
|
|
|
|
environment = setEnv(environment, "RUNFILES_DIR", runfilesDir)
|
|
environment = setEnv(environment, "RUNFILES", runfilesDir)
|
|
return environment
|
|
}
|
|
|
|
func setEnv(environment []string, key string, value string) []string {
|
|
prefix := key + "="
|
|
for index, entry := range environment {
|
|
if strings.HasPrefix(entry, prefix) {
|
|
environment[index] = prefix + value
|
|
return environment
|
|
}
|
|
}
|
|
return append(environment, prefix+value)
|
|
}
|
|
|
|
func waitForURL(url string, timeout time.Duration, waitCh <-chan error) error {
|
|
client := http.Client{Timeout: 2 * time.Second}
|
|
deadline := time.Now().Add(timeout)
|
|
|
|
for time.Now().Before(deadline) {
|
|
select {
|
|
case err := <-waitCh:
|
|
if err == nil {
|
|
return fmt.Errorf("frontend dev server exited before becoming ready")
|
|
}
|
|
return fmt.Errorf("frontend dev server exited before becoming ready: %w", err)
|
|
default:
|
|
}
|
|
|
|
response, err := client.Get(url)
|
|
if err == nil {
|
|
response.Body.Close()
|
|
if response.StatusCode >= 200 && response.StatusCode < 400 {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
time.Sleep(250 * time.Millisecond)
|
|
}
|
|
|
|
return fmt.Errorf("frontend dev server did not become ready at %s", url)
|
|
}
|
|
|
|
func resolveWatchCommand(target string) (*exec.Cmd, error) {
|
|
for _, candidate := range []string{"ibazel", "bazelisk", "bazel"} {
|
|
if _, err := exec.LookPath(candidate); err == nil {
|
|
return exec.Command(candidate, "run", target), nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("neither ibazel, bazelisk, nor bazel is available in PATH")
|
|
}
|
|
|
|
func terminate(command *exec.Cmd, waitCh <-chan error) {
|
|
if command.Process == nil {
|
|
return
|
|
}
|
|
_ = command.Process.Kill()
|
|
select {
|
|
case <-waitCh:
|
|
case <-time.After(2 * time.Second):
|
|
}
|
|
}
|
|
|
|
func must(err error) {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func require(condition bool, message string) {
|
|
if !condition {
|
|
panic(message)
|
|
}
|
|
}
|