1package main 2 3import ( 4 "bytes" 5 "fmt" 6 "go/build" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "runtime" 11 "strings" 12 "sync" 13 "unicode/utf8" 14) 15 16// our own readdir, which skips the files it cannot lstat 17func readdir_lstat(name string) ([]os.FileInfo, error) { 18 f, err := os.Open(name) 19 if err != nil { 20 return nil, err 21 } 22 defer f.Close() 23 24 names, err := f.Readdirnames(-1) 25 if err != nil { 26 return nil, err 27 } 28 29 out := make([]os.FileInfo, 0, len(names)) 30 for _, lname := range names { 31 s, err := os.Lstat(filepath.Join(name, lname)) 32 if err != nil { 33 continue 34 } 35 out = append(out, s) 36 } 37 return out, nil 38} 39 40// our other readdir function, only opens and reads 41func readdir(dirname string) []os.FileInfo { 42 f, err := os.Open(dirname) 43 if err != nil { 44 return nil 45 } 46 fi, err := f.Readdir(-1) 47 f.Close() 48 if err != nil { 49 panic(err) 50 } 51 return fi 52} 53 54// returns truncated 'data' and amount of bytes skipped (for cursor pos adjustment) 55func filter_out_shebang(data []byte) ([]byte, int) { 56 if len(data) > 2 && data[0] == '#' && data[1] == '!' { 57 newline := bytes.Index(data, []byte("\n")) 58 if newline != -1 && len(data) > newline+1 { 59 return data[newline+1:], newline + 1 60 } 61 } 62 return data, 0 63} 64 65func file_exists(filename string) bool { 66 _, err := os.Stat(filename) 67 if err != nil { 68 return false 69 } 70 return true 71} 72 73func is_dir(path string) bool { 74 fi, err := os.Stat(path) 75 return err == nil && fi.IsDir() 76} 77 78func char_to_byte_offset(s []byte, offset_c int) (offset_b int) { 79 for offset_b = 0; offset_c > 0 && offset_b < len(s); offset_b++ { 80 if utf8.RuneStart(s[offset_b]) { 81 offset_c-- 82 } 83 } 84 return offset_b 85} 86 87func xdg_home_dir() string { 88 xdghome := os.Getenv("XDG_CONFIG_HOME") 89 if xdghome == "" { 90 xdghome = filepath.Join(os.Getenv("HOME"), ".config") 91 } 92 return xdghome 93} 94 95func has_prefix(s, prefix string, ignorecase bool) bool { 96 if ignorecase { 97 s = strings.ToLower(s) 98 prefix = strings.ToLower(prefix) 99 } 100 return strings.HasPrefix(s, prefix) 101} 102 103func find_bzl_project_root(libpath, path string) (string, error) { 104 if libpath == "" { 105 return "", fmt.Errorf("could not find project root, libpath is empty") 106 } 107 108 pathMap := map[string]struct{}{} 109 for _, lp := range strings.Split(libpath, ":") { 110 lp := strings.TrimSpace(lp) 111 pathMap[filepath.Clean(lp)] = struct{}{} 112 } 113 114 path = filepath.Dir(path) 115 if path == "" { 116 return "", fmt.Errorf("project root is blank") 117 } 118 119 start := path 120 for path != "/" { 121 if _, ok := pathMap[filepath.Clean(path)]; ok { 122 return path, nil 123 } 124 path = filepath.Dir(path) 125 } 126 return "", fmt.Errorf("could not find project root in %q or its parents", start) 127} 128 129// Code taken directly from `gb`, I hope author doesn't mind. 130func find_gb_project_root(path string) (string, error) { 131 path = filepath.Dir(path) 132 if path == "" { 133 return "", fmt.Errorf("project root is blank") 134 } 135 start := path 136 for path != "/" { 137 root := filepath.Join(path, "src") 138 if _, err := os.Stat(root); err != nil { 139 if os.IsNotExist(err) { 140 path = filepath.Dir(path) 141 continue 142 } 143 return "", err 144 } 145 path, err := filepath.EvalSymlinks(path) 146 if err != nil { 147 return "", err 148 } 149 return path, nil 150 } 151 return "", fmt.Errorf("could not find project root in %q or its parents", start) 152} 153 154// vendorlessImportPath returns the devendorized version of the provided import path. 155// e.g. "foo/bar/vendor/a/b" => "a/b" 156func vendorlessImportPath(ipath string, currentPackagePath string) (string, bool) { 157 split := strings.Split(ipath, "vendor/") 158 // no vendor in path 159 if len(split) == 1 { 160 return ipath, true 161 } 162 // this import path does not belong to the current package 163 if currentPackagePath != "" && !strings.Contains(currentPackagePath, split[0]) { 164 if split[0] != currentPackagePath+"/" { 165 return "", false 166 } 167 } 168 // Devendorize for use in import statement. 169 if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 { 170 return ipath[i+len("/vendor/"):], true 171 } 172 if strings.HasPrefix(ipath, "vendor/") { 173 return ipath[len("vendor/"):], true 174 } 175 return ipath, true 176} 177 178func internalImportPath(ipath string, currentPackagePath string) (string, bool) { 179 split := strings.Split(ipath, "/internal") 180 // no vendor in path 181 // if len(split) == 1 { 182 // return "", false 183 // } 184 // this import path does not belong to the current package 185 if currentPackagePath != "" && !strings.Contains(currentPackagePath, split[0]) { 186 if split[0] != currentPackagePath+"/" { 187 return "", false 188 } 189 } 190 return ipath, true 191} 192 193//------------------------------------------------------------------------- 194// print_backtrace 195// 196// a nicer backtrace printer than the default one 197//------------------------------------------------------------------------- 198 199var g_backtrace_mutex sync.Mutex 200 201func print_backtrace(err interface{}) { 202 g_backtrace_mutex.Lock() 203 defer g_backtrace_mutex.Unlock() 204 fmt.Printf("panic: %v\n", err) 205 i := 2 206 for { 207 pc, file, line, ok := runtime.Caller(i) 208 if !ok { 209 break 210 } 211 f := runtime.FuncForPC(pc) 212 fmt.Printf("%d(%s): %s:%d\n", i-1, f.Name(), file, line) 213 i++ 214 } 215 fmt.Println("") 216} 217 218//------------------------------------------------------------------------- 219// File reader goroutine 220// 221// It's a bad idea to block multiple goroutines on file I/O. Creates many 222// threads which fight for HDD. Therefore only single goroutine should read HDD 223// at the same time. 224//------------------------------------------------------------------------- 225 226type file_read_request struct { 227 filename string 228 out chan file_read_response 229} 230 231type file_read_response struct { 232 data []byte 233 error error 234} 235 236type file_reader_type struct { 237 in chan file_read_request 238} 239 240func new_file_reader() *file_reader_type { 241 this := new(file_reader_type) 242 this.in = make(chan file_read_request) 243 go func() { 244 var rsp file_read_response 245 for { 246 req := <-this.in 247 rsp.data, rsp.error = ioutil.ReadFile(req.filename) 248 req.out <- rsp 249 } 250 }() 251 return this 252} 253 254func (this *file_reader_type) read_file(filename string) ([]byte, error) { 255 req := file_read_request{ 256 filename, 257 make(chan file_read_response), 258 } 259 this.in <- req 260 rsp := <-req.out 261 return rsp.data, rsp.error 262} 263 264var file_reader = new_file_reader() 265 266//------------------------------------------------------------------------- 267// copy of the build.Context without func fields 268//------------------------------------------------------------------------- 269 270type go_build_context struct { 271 GOARCH string 272 GOOS string 273 GOROOT string 274 GOPATH string 275 CgoEnabled bool 276 UseAllFiles bool 277 Compiler string 278 BuildTags []string 279 ReleaseTags []string 280 InstallSuffix string 281} 282 283func pack_build_context(ctx *build.Context) go_build_context { 284 return go_build_context{ 285 GOARCH: ctx.GOARCH, 286 GOOS: ctx.GOOS, 287 GOROOT: ctx.GOROOT, 288 GOPATH: ctx.GOPATH, 289 CgoEnabled: ctx.CgoEnabled, 290 UseAllFiles: ctx.UseAllFiles, 291 Compiler: ctx.Compiler, 292 BuildTags: ctx.BuildTags, 293 ReleaseTags: ctx.ReleaseTags, 294 InstallSuffix: ctx.InstallSuffix, 295 } 296} 297 298func unpack_build_context(ctx *go_build_context) package_lookup_context { 299 return package_lookup_context{ 300 Context: build.Context{ 301 GOARCH: ctx.GOARCH, 302 GOOS: ctx.GOOS, 303 GOROOT: ctx.GOROOT, 304 GOPATH: ctx.GOPATH, 305 CgoEnabled: ctx.CgoEnabled, 306 UseAllFiles: ctx.UseAllFiles, 307 Compiler: ctx.Compiler, 308 BuildTags: ctx.BuildTags, 309 ReleaseTags: ctx.ReleaseTags, 310 InstallSuffix: ctx.InstallSuffix, 311 }, 312 } 313} 314