util.go (11142B)
1 // Copyright 2020 The CCGO Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // generator.go helpers 6 7 package ccgo // import "modernc.org/ccgo/v3/lib" 8 9 import ( 10 "archive/tar" 11 "bufio" 12 "bytes" 13 "compress/gzip" 14 "fmt" 15 "io" 16 "io/ioutil" 17 "os" 18 "os/exec" 19 "path/filepath" 20 "runtime/debug" 21 "strings" 22 ) 23 24 // CopyFile copies src to dest, preserving permissions and times where/when 25 // possible. If canOverwrite is not nil, it is consulted whether a destination 26 // file can be overwritten. If canOverwrite is nil then destination is 27 // overwritten if permissions allow that, otherwise the function fails. 28 // 29 // Src and dst must be in the slash form. 30 func CopyFile(dst, src string, canOverwrite func(fn string, fi os.FileInfo) bool) (n int64, rerr error) { 31 dst = filepath.FromSlash(dst) 32 dstDir := filepath.Dir(dst) 33 di, err := os.Stat(dstDir) 34 switch { 35 case err != nil: 36 if !os.IsNotExist(err) { 37 return 0, err 38 } 39 40 if err := os.MkdirAll(dstDir, 0770); err != nil { 41 return 0, err 42 } 43 case err == nil: 44 if !di.IsDir() { 45 return 0, fmt.Errorf("cannot create directory, file exists: %s", dst) 46 } 47 } 48 49 src = filepath.FromSlash(src) 50 si, err := os.Stat(src) 51 if err != nil { 52 return 0, err 53 } 54 55 if si.IsDir() { 56 return 0, fmt.Errorf("cannot copy a directory: %s", src) 57 } 58 59 di, err = os.Stat(dst) 60 switch { 61 case err != nil && !os.IsNotExist(err): 62 return 0, err 63 case err == nil: 64 if di.IsDir() { 65 return 0, fmt.Errorf("cannot overwite a directory: %s", dst) 66 } 67 68 if canOverwrite != nil && !canOverwrite(dst, di) { 69 return 0, fmt.Errorf("cannot overwite: %s", dst) 70 } 71 } 72 73 s, err := os.Open(src) 74 if err != nil { 75 return 0, err 76 } 77 78 defer s.Close() 79 r := bufio.NewReader(s) 80 81 d, err := os.Create(dst) 82 83 defer func() { 84 if err := d.Close(); err != nil && rerr == nil { 85 rerr = err 86 return 87 } 88 89 if err := os.Chmod(dst, si.Mode()); err != nil && rerr == nil { 90 rerr = err 91 return 92 } 93 94 if err := os.Chtimes(dst, si.ModTime(), si.ModTime()); err != nil && rerr == nil { 95 rerr = err 96 return 97 } 98 }() 99 100 w := bufio.NewWriter(d) 101 102 defer func() { 103 if err := w.Flush(); err != nil && rerr == nil { 104 rerr = err 105 } 106 }() 107 108 return io.Copy(w, r) 109 } 110 111 // MustCopyFile is like CopyFile but it executes Fatal(stackTrace, err) if it fails. 112 func MustCopyFile(stackTrace bool, dst, src string, canOverwrite func(fn string, fi os.FileInfo) bool) int64 { 113 n, err := CopyFile(dst, src, canOverwrite) 114 if err != nil { 115 Fatal(stackTrace, err) 116 } 117 118 return n 119 } 120 121 // CopyDir recursively copies src to dest, preserving permissions and times 122 // where/when possible. If canOverwrite is not nil, it is consulted whether a 123 // destination file can be overwritten. If canOverwrite is nil then destination 124 // is overwritten if permissions allow that, otherwise the function fails. 125 // 126 // Src and dst must be in the slash form. 127 func CopyDir(dst, src string, canOverwrite func(fn string, fi os.FileInfo) bool) (files int, bytes int64, rerr error) { 128 dst = filepath.FromSlash(dst) 129 src = filepath.FromSlash(src) 130 si, err := os.Stat(src) 131 if err != nil { 132 return 0, 0, err 133 } 134 135 if !si.IsDir() { 136 return 0, 0, fmt.Errorf("cannot copy a file: %s", src) 137 } 138 139 return files, bytes, filepath.Walk(src, func(path string, info os.FileInfo, err error) error { 140 if err != nil { 141 return err 142 } 143 144 rel, err := filepath.Rel(src, path) 145 if err != nil { 146 return err 147 } 148 149 if info.IsDir() { 150 return os.MkdirAll(filepath.Join(dst, rel), 0770) 151 } 152 153 n, err := CopyFile(filepath.Join(dst, rel), path, canOverwrite) 154 if err != nil { 155 return err 156 } 157 158 files++ 159 bytes += n 160 return nil 161 }) 162 } 163 164 // MustCopyDir is like CopyDir, but it executes Fatal(stackTrace, errĂº if it fails. 165 func MustCopyDir(stackTrace bool, dst, src string, canOverwrite func(fn string, fi os.FileInfo) bool) (files int, bytes int64) { 166 file, bytes, err := CopyDir(dst, src, canOverwrite) 167 if err != nil { 168 Fatal(stackTrace, err) 169 } 170 171 return file, bytes 172 } 173 174 // UntarFile extracts a named tar.gz archive into dst. If canOverwrite is not 175 // nil, it is consulted whether a destination file can be overwritten. If 176 // canOverwrite is nil then destination is overwritten if permissions allow 177 // that, otherwise the function fails. 178 // 179 // Src and dst must be in the slash form. 180 func UntarFile(dst, src string, canOverwrite func(fn string, fi os.FileInfo) bool) error { 181 f, err := os.Open(filepath.FromSlash(src)) 182 if err != nil { 183 return err 184 } 185 186 defer f.Close() 187 188 return Untar(dst, bufio.NewReader(f), canOverwrite) 189 } 190 191 // MustUntarFile is like UntarFile but it executes Fatal(stackTrace, err) if it fails. 192 func MustUntarFile(stackTrace bool, dst, src string, canOverwrite func(fn string, fi os.FileInfo) bool) { 193 if err := UntarFile(dst, src, canOverwrite); err != nil { 194 Fatal(stackTrace, err) 195 } 196 } 197 198 // Untar extracts a tar.gz archive into dst. If canOverwrite is not nil, it is 199 // consulted whether a destination file can be overwritten. If canOverwrite is 200 // nil then destination is overwritten if permissions allow that, otherwise the 201 // function fails. 202 // 203 // Dst must be in the slash form. 204 func Untar(dst string, r io.Reader, canOverwrite func(fn string, fi os.FileInfo) bool) error { 205 dst = filepath.FromSlash(dst) 206 gr, err := gzip.NewReader(r) 207 if err != nil { 208 return err 209 } 210 211 tr := tar.NewReader(gr) 212 for { 213 hdr, err := tr.Next() 214 if err != nil { 215 if err != io.EOF { 216 return err 217 } 218 219 return nil 220 } 221 222 switch hdr.Typeflag { 223 case tar.TypeDir: 224 dir := filepath.Join(dst, hdr.Name) 225 if err = os.MkdirAll(dir, 0770); err != nil { 226 return err 227 } 228 case tar.TypeSymlink, tar.TypeXGlobalHeader: 229 // skip 230 case tar.TypeReg, tar.TypeRegA: 231 dir := filepath.Dir(filepath.Join(dst, hdr.Name)) 232 if _, err := os.Stat(dir); err != nil { 233 if !os.IsNotExist(err) { 234 return err 235 } 236 237 if err = os.MkdirAll(dir, 0770); err != nil { 238 return err 239 } 240 } 241 242 fn := filepath.Join(dst, hdr.Name) 243 f, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, os.FileMode(hdr.Mode)) 244 if err != nil { 245 return err 246 } 247 248 w := bufio.NewWriter(f) 249 if _, err = io.Copy(w, tr); err != nil { 250 return err 251 } 252 253 if err := w.Flush(); err != nil { 254 return err 255 } 256 257 if err := f.Close(); err != nil { 258 return err 259 } 260 261 if err := os.Chtimes(fn, hdr.AccessTime, hdr.ModTime); err != nil { 262 return err 263 } 264 default: 265 return fmt.Errorf("unexpected tar header typeflag %#02x", hdr.Typeflag) 266 } 267 } 268 269 } 270 271 // MustUntar is like Untar but it executes Fatal(stackTrace, err) if it fails. 272 func MustUntar(stackTrace bool, dst string, r io.Reader, canOverwrite func(fn string, fi os.FileInfo) bool) { 273 if err := Untar(dst, r, canOverwrite); err != nil { 274 Fatal(stackTrace, err) 275 } 276 } 277 278 // Fatalf prints a formatted message to os.Stderr and performs os.Exit(1). A 279 // stack trace is added if stackTrace is true. 280 func Fatalf(stackTrace bool, s string, args ...interface{}) { 281 if stackTrace { 282 fmt.Fprintf(os.Stderr, "%s\n", debug.Stack()) 283 } 284 fmt.Fprintln(os.Stderr, strings.TrimSpace(fmt.Sprintf(s, args...))) 285 os.Exit(1) 286 } 287 288 // Fatal prints its argumenst to os.Stderr and performs os.Exit(1). A 289 // stack trace is added if stackTrace is true. 290 func Fatal(stackTrace bool, args ...interface{}) { 291 if stackTrace { 292 fmt.Fprintf(os.Stderr, "%s\n", debug.Stack()) 293 } 294 fmt.Fprintln(os.Stderr, strings.TrimSpace(fmt.Sprint(args...))) 295 os.Exit(1) 296 } 297 298 // Mkdirs will create all paths. Paths must be in slash form. 299 func Mkdirs(paths ...string) error { 300 for _, path := range paths { 301 path = filepath.FromSlash(path) 302 if err := os.MkdirAll(path, 0770); err != nil { 303 return err 304 } 305 } 306 307 return nil 308 } 309 310 // MustMkdirs is like Mkdir but if executes Fatal(stackTrace, err) if it fails. 311 func MustMkdirs(stackTrace bool, paths ...string) { 312 if err := Mkdirs(paths...); err != nil { 313 Fatal(stackTrace, err) 314 } 315 } 316 317 // InDir executes f in dir. Dir must be in slash form. 318 func InDir(dir string, f func() error) (err error) { 319 var cwd string 320 if cwd, err = os.Getwd(); err != nil { 321 return err 322 } 323 324 defer func() { 325 if err2 := os.Chdir(cwd); err2 != nil { 326 err = err2 327 } 328 }() 329 330 if err = os.Chdir(filepath.FromSlash(dir)); err != nil { 331 return err 332 } 333 334 return f() 335 } 336 337 // MustInDir is like InDir but it executes Fatal(stackTrace, err) if it fails. 338 func MustInDir(stackTrace bool, dir string, f func() error) { 339 if err := InDir(dir, f); err != nil { 340 Fatal(stackTrace, err) 341 } 342 } 343 344 type echoWriter struct { 345 w bytes.Buffer 346 } 347 348 func (w *echoWriter) Write(b []byte) (int, error) { 349 os.Stdout.Write(b) 350 return w.w.Write(b) 351 } 352 353 // Shell echoes and executes cmd with args and returns the combined output if the command. 354 func Shell(cmd string, args ...string) ([]byte, error) { 355 cmd, err := exec.LookPath(cmd) 356 if err != nil { 357 return nil, err 358 } 359 360 wd, err := AbsCwd() 361 if err != nil { 362 return nil, err 363 } 364 365 fmt.Printf("execute %s %q in %s\n", cmd, args, wd) 366 var b echoWriter 367 c := exec.Command(cmd, args...) 368 c.Stdout = &b 369 c.Stderr = &b 370 err = c.Run() 371 return b.w.Bytes(), err 372 } 373 374 // MustShell is like Shell but it executes Fatal(stackTrace, err) if it fails. 375 func MustShell(stackTrace bool, cmd string, args ...string) []byte { 376 b, err := Shell(cmd, args...) 377 if err != nil { 378 Fatalf(stackTrace, "%v %s\noutput: %s\nerr: %s", cmd, args, b, err) 379 } 380 381 return b 382 } 383 384 // Compile executes Shell with cmd set to "ccgo". 385 func Compile(args ...string) ([]byte, error) { return Shell("ccgo", args...) } 386 387 // MustCompile is like Compile but if executes Fatal(stackTrace, err) if it fails. 388 func MustCompile(stackTrace bool, args ...string) []byte { 389 return MustShell(stackTrace, "ccgo", args...) 390 } 391 392 // Run is like Compile, but executes in-process. 393 func Run(args ...string) ([]byte, error) { 394 var b bytes.Buffer 395 t := NewTask(append([]string{"ccgo"}, args...), &b, &b) 396 err := t.Main() 397 return b.Bytes(), err 398 } 399 400 // MustRun is like Run but if executes Fatal(stackTrace, err) if it fails. 401 func MustRun(stackTrace bool, args ...string) []byte { 402 var b bytes.Buffer 403 args = append([]string{"ccgo"}, args...) 404 t := NewTask(args, &b, &b) 405 if err := t.Main(); err != nil { 406 Fatalf(stackTrace, "%v\noutput: %s\nerr: %s", args, b.Bytes(), err) 407 } 408 409 return b.Bytes() 410 } 411 412 // AbsCwd returns the absolute working directory. 413 func AbsCwd() (string, error) { 414 wd, err := os.Getwd() 415 if err != nil { 416 return "", err 417 } 418 419 if wd, err = filepath.Abs(wd); err != nil { 420 return "", err 421 } 422 423 return wd, nil 424 } 425 426 // MustAbsCwd is like AbsCwd but executes Fatal(stackTrace, err) if it fails. 427 func MustAbsCwd(stackTrace bool) string { 428 s, err := AbsCwd() 429 if err != nil { 430 Fatal(stackTrace, err) 431 } 432 433 return s 434 } 435 436 // Env returns the value of environmental variable key of dflt otherwise. 437 func Env(key, dflt string) string { 438 if s := os.Getenv(key); s != "" { 439 return s 440 } 441 442 return dflt 443 } 444 445 // MustTempDir is like ioutil.TempDir but executes Fatal(stackTrace, err) if it 446 // fails. The returned path is absolute. 447 func MustTempDir(stackTrace bool, dir, name string) string { 448 s, err := ioutil.TempDir(dir, name) 449 if err != nil { 450 Fatal(stackTrace, err) 451 } 452 453 if s, err = filepath.Abs(s); err != nil { 454 Fatal(stackTrace, err) 455 } 456 457 return s 458 }