You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
5.8 KiB
Go

package runner
import (
"crypto/sha256"
"encoding/hex"
"errors"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"github.com/fsnotify/fsnotify"
)
func (e *Engine) mainLog(format string, v ...interface{}) {
e.logWithLock(func() {
e.logger.main()(format, v...)
})
}
func (e *Engine) mainDebug(format string, v ...interface{}) {
if e.debugMode {
e.mainLog(format, v...)
}
}
func (e *Engine) buildLog(format string, v ...interface{}) {
e.logWithLock(func() {
e.logger.build()(format, v...)
})
}
func (e *Engine) runnerLog(format string, v ...interface{}) {
e.logWithLock(func() {
e.logger.runner()(format, v...)
})
}
func (e *Engine) watcherLog(format string, v ...interface{}) {
e.logWithLock(func() {
e.logger.watcher()(format, v...)
})
}
func (e *Engine) watcherDebug(format string, v ...interface{}) {
if e.debugMode {
e.watcherLog(format, v...)
}
}
func (e *Engine) isTmpDir(path string) bool {
return path == e.config.tmpPath()
}
func isHiddenDirectory(path string) bool {
return len(path) > 1 && strings.HasPrefix(filepath.Base(path), ".")
}
func cleanPath(path string) string {
return strings.TrimSuffix(strings.TrimSpace(path), "/")
}
func (e *Engine) isExcludeDir(path string) bool {
cleanName := cleanPath(e.config.rel(path))
for _, d := range e.config.Build.ExcludeDir {
if cleanName == d {
return true
}
}
return false
}
// return isIncludeDir, walkDir
func (e *Engine) checkIncludeDir(path string) (bool, bool) {
cleanName := cleanPath(e.config.rel(path))
iDir := e.config.Build.IncludeDir
if len(iDir) == 0 { // ignore empty
return true, true
}
if cleanName == "." {
return false, true
}
walkDir := false
for _, d := range iDir {
if d == cleanName {
return true, true
}
if strings.HasPrefix(cleanName, d) { // current dir is sub-directory of `d`
return true, true
}
if strings.HasPrefix(d, cleanName) { // `d` is sub-directory of current dir
walkDir = true
}
}
return false, walkDir
}
func (e *Engine) isIncludeExt(path string) bool {
ext := filepath.Ext(path)
for _, v := range e.config.Build.IncludeExt {
if ext == "."+strings.TrimSpace(v) {
return true
}
}
return false
}
func (e *Engine) isExcludeRegex(path string) (bool, error) {
regexes, err := e.config.Build.RegexCompiled()
if err != nil {
return false, err
}
for _, re := range regexes {
if re.Match([]byte(path)) {
return true, nil
}
}
return false, nil
}
func (e *Engine) isExcludeFile(path string) bool {
cleanName := cleanPath(e.config.rel(path))
for _, d := range e.config.Build.ExcludeFile {
matched, err := filepath.Match(d, cleanName)
if err == nil && matched {
return true
}
}
return false
}
func (e *Engine) writeBuildErrorLog(msg string) error {
var err error
f, err := os.OpenFile(e.config.buildLogPath(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
if _, err = f.Write([]byte(msg)); err != nil {
return err
}
return f.Close()
}
func (e *Engine) withLock(f func()) {
e.mu.Lock()
f()
e.mu.Unlock()
}
func (e *Engine) logWithLock(f func()) {
e.ll.Lock()
f()
e.ll.Unlock()
}
func expandPath(path string) (string, error) {
if strings.HasPrefix(path, "~/") {
home := os.Getenv("HOME")
return home + path[1:], nil
}
var err error
wd, err := os.Getwd()
if err != nil {
return "", err
}
if path == "." {
return wd, nil
}
if strings.HasPrefix(path, "./") {
return wd + path[1:], nil
}
return path, nil
}
func isDir(path string) bool {
i, err := os.Stat(path)
if err != nil {
return false
}
return i.IsDir()
}
func validEvent(ev fsnotify.Event) bool {
return ev.Op&fsnotify.Create == fsnotify.Create ||
ev.Op&fsnotify.Write == fsnotify.Write ||
ev.Op&fsnotify.Remove == fsnotify.Remove
}
func removeEvent(ev fsnotify.Event) bool {
return ev.Op&fsnotify.Remove == fsnotify.Remove
}
func cmdPath(path string) string {
return strings.Split(path, " ")[0]
}
func adaptToVariousPlatforms(c *config) {
// Fix the default configuration is not used in Windows
// Use the unix configuration on Windows
if runtime.GOOS == PlatformWindows {
runName := "start"
extName := ".exe"
originBin := c.Build.Bin
if !strings.HasSuffix(c.Build.Bin, extName) {
c.Build.Bin += extName
}
if 0 < len(c.Build.FullBin) {
if !strings.HasSuffix(c.Build.FullBin, extName) {
c.Build.FullBin += extName
}
if !strings.HasPrefix(c.Build.FullBin, runName) {
c.Build.FullBin = runName + " /b " + c.Build.FullBin
}
}
// bin=/tmp/main cmd=go build -o ./tmp/main.exe main.go
if !strings.Contains(c.Build.Cmd, c.Build.Bin) && strings.Contains(c.Build.Cmd, originBin) {
c.Build.Cmd = strings.Replace(c.Build.Cmd, originBin, c.Build.Bin, 1)
}
}
}
// fileChecksum returns a checksum for the given file's contents.
func fileChecksum(filename string) (checksum string, err error) {
contents, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}
// If the file is empty, an editor might've been in the process of rewriting the file when we read it.
// This can happen often if editors are configured to run format after save.
// Instead of calculating a new checksum, we'll assume the file was unchanged, but return an error to force a rebuild anyway.
if len(contents) == 0 {
return "", errors.New("empty file, forcing rebuild without updating checksum")
}
h := sha256.New()
if _, err := h.Write(contents); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}
// checksumMap is a thread-safe map to store file checksums.
type checksumMap struct {
l sync.Mutex
m map[string]string
}
// update updates the filename with the given checksum if different.
func (a *checksumMap) updateFileChecksum(filename, newChecksum string) (ok bool) {
a.l.Lock()
defer a.l.Unlock()
oldChecksum, ok := a.m[filename]
if !ok || oldChecksum != newChecksum {
a.m[filename] = newChecksum
return true
}
return false
}