github.com/yang-ricky/air@v1.30.0/runner/util.go (about) 1 package runner 2 3 import ( 4 "crypto/sha256" 5 "encoding/hex" 6 "errors" 7 "os" 8 "path/filepath" 9 "runtime" 10 "strings" 11 "sync" 12 13 "github.com/fsnotify/fsnotify" 14 ) 15 16 func (e *Engine) mainLog(format string, v ...interface{}) { 17 e.logWithLock(func() { 18 e.logger.main()(format, v...) 19 }) 20 } 21 22 func (e *Engine) mainDebug(format string, v ...interface{}) { 23 if e.debugMode { 24 e.mainLog(format, v...) 25 } 26 } 27 28 func (e *Engine) buildLog(format string, v ...interface{}) { 29 e.logWithLock(func() { 30 e.logger.build()(format, v...) 31 }) 32 } 33 34 func (e *Engine) runnerLog(format string, v ...interface{}) { 35 e.logWithLock(func() { 36 e.logger.runner()(format, v...) 37 }) 38 } 39 40 func (e *Engine) watcherLog(format string, v ...interface{}) { 41 e.logWithLock(func() { 42 e.logger.watcher()(format, v...) 43 }) 44 } 45 46 func (e *Engine) watcherDebug(format string, v ...interface{}) { 47 if e.debugMode { 48 e.watcherLog(format, v...) 49 } 50 } 51 52 func (e *Engine) isTmpDir(path string) bool { 53 return path == e.config.tmpPath() 54 } 55 56 func (e *Engine) isTestDataDir(path string) bool { 57 return path == e.config.TestDataPath() 58 } 59 60 func isHiddenDirectory(path string) bool { 61 return len(path) > 1 && strings.HasPrefix(filepath.Base(path), ".") 62 } 63 64 func cleanPath(path string) string { 65 return strings.TrimSuffix(strings.TrimSpace(path), "/") 66 } 67 68 func (e *Engine) isExcludeDir(path string) bool { 69 cleanName := cleanPath(e.config.rel(path)) 70 for _, d := range e.config.Build.ExcludeDir { 71 if cleanName == d { 72 return true 73 } 74 } 75 return false 76 } 77 78 // return isIncludeDir, walkDir 79 func (e *Engine) checkIncludeDir(path string) (bool, bool) { 80 cleanName := cleanPath(e.config.rel(path)) 81 iDir := e.config.Build.IncludeDir 82 if len(iDir) == 0 { // ignore empty 83 return true, true 84 } 85 if cleanName == "." { 86 return false, true 87 } 88 walkDir := false 89 for _, d := range iDir { 90 if d == cleanName { 91 return true, true 92 } 93 if strings.HasPrefix(cleanName, d) { // current dir is sub-directory of `d` 94 return true, true 95 } 96 if strings.HasPrefix(d, cleanName) { // `d` is sub-directory of current dir 97 walkDir = true 98 } 99 } 100 return false, walkDir 101 } 102 103 func (e *Engine) isIncludeExt(path string) bool { 104 ext := filepath.Ext(path) 105 for _, v := range e.config.Build.IncludeExt { 106 if ext == "."+strings.TrimSpace(v) { 107 return true 108 } 109 } 110 return false 111 } 112 113 func (e *Engine) isExcludeRegex(path string) (bool, error) { 114 regexes, err := e.config.Build.RegexCompiled() 115 if err != nil { 116 return false, err 117 } 118 for _, re := range regexes { 119 if re.Match([]byte(path)) { 120 return true, nil 121 } 122 } 123 return false, nil 124 } 125 126 func (e *Engine) isExcludeFile(path string) bool { 127 cleanName := cleanPath(e.config.rel(path)) 128 for _, d := range e.config.Build.ExcludeFile { 129 matched, err := filepath.Match(d, cleanName) 130 if err == nil && matched { 131 return true 132 } 133 } 134 return false 135 } 136 137 func (e *Engine) writeBuildErrorLog(msg string) error { 138 var err error 139 f, err := os.OpenFile(e.config.buildLogPath(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 140 if err != nil { 141 return err 142 } 143 if _, err = f.Write([]byte(msg)); err != nil { 144 return err 145 } 146 return f.Close() 147 } 148 149 func (e *Engine) withLock(f func()) { 150 e.mu.Lock() 151 f() 152 e.mu.Unlock() 153 } 154 155 func (e *Engine) logWithLock(f func()) { 156 e.ll.Lock() 157 f() 158 e.ll.Unlock() 159 } 160 161 func expandPath(path string) (string, error) { 162 if strings.HasPrefix(path, "~/") { 163 home := os.Getenv("HOME") 164 return home + path[1:], nil 165 } 166 var err error 167 wd, err := os.Getwd() 168 if err != nil { 169 return "", err 170 } 171 if path == "." { 172 return wd, nil 173 } 174 if strings.HasPrefix(path, "./") { 175 return wd + path[1:], nil 176 } 177 return path, nil 178 } 179 180 func isDir(path string) bool { 181 i, err := os.Stat(path) 182 if err != nil { 183 return false 184 } 185 return i.IsDir() 186 } 187 188 func validEvent(ev fsnotify.Event) bool { 189 return ev.Op&fsnotify.Create == fsnotify.Create || 190 ev.Op&fsnotify.Write == fsnotify.Write || 191 ev.Op&fsnotify.Remove == fsnotify.Remove 192 } 193 194 func removeEvent(ev fsnotify.Event) bool { 195 return ev.Op&fsnotify.Remove == fsnotify.Remove 196 } 197 198 func cmdPath(path string) string { 199 return strings.Split(path, " ")[0] 200 } 201 202 func adaptToVariousPlatforms(c *config) { 203 // Fix the default configuration is not used in Windows 204 // Use the unix configuration on Windows 205 if runtime.GOOS == PlatformWindows { 206 207 runName := "start" 208 extName := ".exe" 209 originBin := c.Build.Bin 210 211 if 0 < len(c.Build.FullBin) { 212 213 if !strings.HasSuffix(c.Build.FullBin, extName) { 214 215 c.Build.FullBin += extName 216 } 217 if !strings.HasPrefix(c.Build.FullBin, runName) { 218 c.Build.FullBin = runName + " /wait /b " + c.Build.FullBin 219 } 220 } 221 222 // bin=/tmp/main cmd=go build -o ./tmp/main.exe main.go 223 if !strings.Contains(c.Build.Cmd, c.Build.Bin) && strings.Contains(c.Build.Cmd, originBin) { 224 c.Build.Cmd = strings.Replace(c.Build.Cmd, originBin, c.Build.Bin, 1) 225 } 226 } 227 } 228 229 // fileChecksum returns a checksum for the given file's contents. 230 func fileChecksum(filename string) (checksum string, err error) { 231 contents, err := os.ReadFile(filename) 232 if err != nil { 233 return "", err 234 } 235 236 // If the file is empty, an editor might've been in the process of rewriting the file when we read it. 237 // This can happen often if editors are configured to run format after save. 238 // Instead of calculating a new checksum, we'll assume the file was unchanged, but return an error to force a rebuild anyway. 239 if len(contents) == 0 { 240 return "", errors.New("empty file, forcing rebuild without updating checksum") 241 } 242 243 h := sha256.New() 244 if _, err := h.Write(contents); err != nil { 245 return "", err 246 } 247 248 return hex.EncodeToString(h.Sum(nil)), nil 249 } 250 251 // checksumMap is a thread-safe map to store file checksums. 252 type checksumMap struct { 253 l sync.Mutex 254 m map[string]string 255 } 256 257 // update updates the filename with the given checksum if different. 258 func (a *checksumMap) updateFileChecksum(filename, newChecksum string) (ok bool) { 259 a.l.Lock() 260 defer a.l.Unlock() 261 oldChecksum, ok := a.m[filename] 262 if !ok || oldChecksum != newChecksum { 263 a.m[filename] = newChecksum 264 return true 265 } 266 return false 267 }