util.go 5.73 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package build

import (
20
	"bufio"
21 22 23
	"bytes"
	"flag"
	"fmt"
24 25
	"go/parser"
	"go/token"
26
	"io"
27 28 29
	"log"
	"os"
	"os/exec"
30
	"path"
31
	"path/filepath"
32
	"strconv"
33 34
	"strings"
	"text/template"
35
	"time"
36 37
)

38
var DryRunFlag = flag.Bool("n", false, "dry run, don't execute commands")
39 40 41 42

// MustRun executes the given command and exits the host process for
// any error.
func MustRun(cmd *exec.Cmd) {
43
	fmt.Println(">>>", printArgs(cmd.Args))
44 45 46 47 48 49 50 51 52
	if !*DryRunFlag {
		cmd.Stderr = os.Stderr
		cmd.Stdout = os.Stdout
		if err := cmd.Run(); err != nil {
			log.Fatal(err)
		}
	}
}

53 54 55 56 57 58 59 60 61 62 63 64 65 66
func printArgs(args []string) string {
	var s strings.Builder
	for i, arg := range args {
		if i > 0 {
			s.WriteByte(' ')
		}
		if strings.IndexByte(arg, ' ') >= 0 {
			arg = strconv.QuoteToASCII(arg)
		}
		s.WriteString(arg)
	}
	return s.String()
}

67 68 69 70
func MustRunCommand(cmd string, args ...string) {
	MustRun(exec.Command(cmd, args...))
}

71 72
var warnedAboutGit bool

73 74
// RunGit runs a git subcommand and returns its output.
// The command must complete successfully.
75 76 77 78
func RunGit(args ...string) string {
	cmd := exec.Command("git", args...)
	var stdout, stderr bytes.Buffer
	cmd.Stdout, cmd.Stderr = &stdout, &stderr
79 80 81 82 83 84 85
	if err := cmd.Run(); err != nil {
		if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
			if !warnedAboutGit {
				log.Println("Warning: can't find 'git' in PATH")
				warnedAboutGit = true
			}
			return ""
86
		}
87 88 89 90 91
		log.Fatal(strings.Join(cmd.Args, " "), ": ", err, "\n", stderr.String())
	}
	return strings.TrimSpace(stdout.String())
}

92 93
// readGitFile returns content of file in .git directory.
func readGitFile(file string) string {
94
	content, err := os.ReadFile(path.Join(".git", file))
95 96 97 98 99 100
	if err != nil {
		return ""
	}
	return strings.TrimSpace(string(content))
}

101
// Render renders the given template file into outputFile.
102 103 104 105 106
func Render(templateFile, outputFile string, outputPerm os.FileMode, x interface{}) {
	tpl := template.Must(template.ParseFiles(templateFile))
	render(tpl, outputFile, outputPerm, x)
}

107
// RenderString renders the given template string into outputFile.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
func RenderString(templateContent, outputFile string, outputPerm os.FileMode, x interface{}) {
	tpl := template.Must(template.New("").Parse(templateContent))
	render(tpl, outputFile, outputPerm, x)
}

func render(tpl *template.Template, outputFile string, outputPerm os.FileMode, x interface{}) {
	if err := os.MkdirAll(filepath.Dir(outputFile), 0755); err != nil {
		log.Fatal(err)
	}
	out, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_EXCL, outputPerm)
	if err != nil {
		log.Fatal(err)
	}
	if err := tpl.Execute(out, x); err != nil {
		log.Fatal(err)
	}
	if err := out.Close(); err != nil {
		log.Fatal(err)
	}
}
128

129
// UploadSFTP uploads files to a remote host using the sftp command line tool.
130
// The destination host may be specified either as [user@]host: or as a URI in
131 132
// the form sftp://[user@]host[:port].
func UploadSFTP(identityFile, host, dir string, files []string) error {
133
	sftp := exec.Command("sftp")
134 135 136 137 138
	sftp.Stderr = os.Stderr
	if identityFile != "" {
		sftp.Args = append(sftp.Args, "-i", identityFile)
	}
	sftp.Args = append(sftp.Args, host)
139
	fmt.Println(">>>", printArgs(sftp.Args))
140 141 142 143 144 145 146 147
	if *DryRunFlag {
		return nil
	}

	stdin, err := sftp.StdinPipe()
	if err != nil {
		return fmt.Errorf("can't create stdin pipe for sftp: %v", err)
	}
148 149 150 151
	stdout, err := sftp.StdoutPipe()
	if err != nil {
		return fmt.Errorf("can't create stdout pipe for sftp: %v", err)
	}
152 153 154 155 156 157 158
	if err := sftp.Start(); err != nil {
		return err
	}
	in := io.MultiWriter(stdin, os.Stdout)
	for _, f := range files {
		fmt.Fprintln(in, "put", f, path.Join(dir, filepath.Base(f)))
	}
159
	fmt.Fprintln(in, "exit")
160 161 162 163 164 165 166 167
	// Some issue with the PPA sftp server makes it so the server does not
	// respond properly to a 'bye', 'exit' or 'quit' from the client.
	// To work around that, we check the output, and when we see the client
	// exit command, we do a hard exit.
	// See
	// https://github.com/kolban-google/sftp-gcs/issues/23
	// https://github.com/mscdex/ssh2/pull/1111
	aborted := false
168
	go func() {
169 170 171 172 173 174 175 176 177 178
		scanner := bufio.NewScanner(stdout)
		for scanner.Scan() {
			txt := scanner.Text()
			fmt.Println(txt)
			if txt == "sftp> exit" {
				// Give it .5 seconds to exit (server might be fixed), then
				// hard kill it from the outside
				time.Sleep(500 * time.Millisecond)
				aborted = true
				sftp.Process.Kill()
179 180 181
			}
		}
	}()
182
	stdin.Close()
183 184 185 186 187
	err = sftp.Wait()
	if aborted {
		return nil
	}
	return err
188
}
189 190 191 192 193

// FindMainPackages finds all 'main' packages in the given directory and returns their
// package paths.
func FindMainPackages(dir string) []string {
	var commands []string
194
	cmds, err := os.ReadDir(dir)
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
	if err != nil {
		log.Fatal(err)
	}
	for _, cmd := range cmds {
		pkgdir := filepath.Join(dir, cmd.Name())
		pkgs, err := parser.ParseDir(token.NewFileSet(), pkgdir, nil, parser.PackageClauseOnly)
		if err != nil {
			log.Fatal(err)
		}
		for name := range pkgs {
			if name == "main" {
				path := "./" + filepath.ToSlash(pkgdir)
				commands = append(commands, path)
				break
			}
		}
	}
	return commands
}