ccgo.go (56560B)
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 //go:generate stringer -output stringer.go -type=exprMode,opKind 6 7 // Package ccgo implements the ccgo command. 8 package ccgo // import "modernc.org/ccgo/v3/lib" 9 10 import ( 11 "bufio" 12 "bytes" 13 "encoding/csv" 14 "encoding/json" 15 "fmt" 16 "go/ast" 17 "go/build" 18 "go/parser" 19 "go/token" 20 "io" 21 "io/ioutil" 22 "os" 23 "os/exec" 24 "path/filepath" 25 "regexp" 26 "runtime" 27 "runtime/debug" 28 "sort" 29 "strconv" 30 "strings" 31 "time" 32 33 "github.com/kballard/go-shellquote" 34 "golang.org/x/tools/go/packages" 35 "modernc.org/cc/v3" 36 "modernc.org/libc" 37 "modernc.org/opt" 38 ) 39 40 const ( 41 Version = "3.12.6-20210922111124" 42 43 experimentsEnvVar = "CCGO_EXPERIMENT" 44 maxSourceLine = 1 << 20 45 ) 46 47 var ( 48 _ = libc.Xstdin 49 50 coverExperiment bool 51 ) 52 53 func init() { 54 s := strings.TrimSpace(os.Getenv(experimentsEnvVar)) 55 if s == "" { 56 return 57 } 58 59 for _, v := range strings.Split(s, ",") { 60 switch strings.TrimSpace(v) { 61 case "cover": 62 coverExperiment = true 63 } 64 } 65 } 66 67 //TODO CPython 68 //TODO Cython 69 //TODO gmp 70 //TODO gofrontend 71 //TODO gsl 72 //TODO minigmp 73 //TODO mpc 74 //TODO mpfr 75 //TODO pcre 76 //TODO pcre2 77 //TODO quickjs 78 //TODO redis 79 //TODO sdl2 80 //TODO wolfssl 81 //TODO zdat 82 //TODO zstd 83 84 //TODO 2020-07-17 85 // 86 // Fix += and friends 87 // 88 // Audit all unsafe.Pointer conversions 89 // 90 // Remove double dereferencing ** 91 // 92 // Shifts must not use n.Promote on left opearand 93 // 94 // Un-array 95 // 96 // Pass more CSmith tests. 97 98 //TODO merge VaList slots of distinct top level statements. 99 100 //TODO turn void 101 // 102 // a = b = c = d 103 // 104 // where all but the first and last of a, b, c, ... are declarators, into 105 // 106 // c = d 107 // b = c 108 // a = b 109 110 //TODO define and use all tagged struct types, including inner ones, for example SQLite's SrcList_item. 111 112 //TODO rewrite return conditionalExpression so it has no closures. Partially done. 113 114 //TODO define and restore simple named constants. Having 115 // 116 // #define FOO 42 117 // ... 118 // case FOO: 119 // 120 // we do not yet define FOO and generate 121 // 122 // case 42: 123 124 //TODO do not generate a terminating semicolon for empty statements. 125 126 //TODO replace 127 // 128 // var sqlite3_data_directory uintptr = uintptr(0) /* sqlite3.c:156345:17 */ 129 // 130 // by 131 // 132 // var sqlite3_data_directory uintptr = 0 /* sqlite3.c:156345:17 */ 133 // 134 // or 135 // 136 // var sqlite3_data_directory = uintptr(0) /* sqlite3.c:156345:17 */ 137 138 //TODO drop all non-referenced declarators unless forced by a command line flag. 139 140 const ( 141 builtin = ` 142 #ifdef __PTRDIFF_TYPE__ 143 typedef __PTRDIFF_TYPE__ ptrdiff_t; 144 #else 145 #error __PTRDIFF_TYPE__ undefined 146 #endif 147 148 #ifdef __SIZE_TYPE__ 149 typedef __SIZE_TYPE__ size_t; 150 #else 151 #error __SIZE_TYPE__ undefined 152 #endif 153 154 #ifdef __WCHAR_TYPE__ 155 typedef __WCHAR_TYPE__ wchar_t; 156 #else 157 #error __WCHAR_TYPE__ undefined 158 #endif 159 160 #ifdef __SIZEOF_INT128__ 161 typedef struct { __INT64_TYPE__ lo, hi; } __int128_t; // must match modernc.org/mathutil.Int128 162 typedef struct { __UINT64_TYPE__ lo, hi; } __uint128_t; // must match modernc.org/mathutil.Int128 163 #endif; 164 165 #define _FILE_OFFSET_BITS 64 166 #define __FUNCTION__ __func__ 167 #define __PRETTY_FUNCTION__ __func__ 168 #define __asm __asm__ 169 #define __builtin_constant_p(x) __builtin_constant_p_impl(0, x) 170 #define __builtin_offsetof(type, member) ((__SIZE_TYPE__)&(((type*)0)->member)) 171 #define __builtin_va_arg(ap, type) ((type)__ccgo_va_arg(ap)) 172 #define __builtin_va_copy(dst, src) dst = src 173 #define __builtin_va_end(ap) __ccgo_va_end(ap) 174 #define __builtin_va_start(ap, v) __ccgo_va_start(ap) 175 #define __ccgo_fd_zero(set) __builtin_memset(set, 0, sizeof(fd_set)) 176 #define __ccgo_tcl_default_double_rounding(set) ((void)0) 177 #define __ccgo_tcl_ieee_double_rounding(set) ((void)0) 178 #define __extension__ 179 #define __has_include(...) __has_include_impl(#__VA_ARGS__) 180 #define __has_include_impl(x) 181 #define __inline__ inline 182 #define __signed signed 183 #define asm __asm__ 184 #define in6addr_any (*__ccgo_in6addr_anyp()) 185 186 typedef void *__builtin_va_list; 187 typedef long double __float128; 188 189 #if defined(__MINGW32__) || defined(__MINGW64__) 190 typedef __builtin_va_list va_list; 191 int gnu_printf(const char *format, ...); 192 int gnu_scanf(const char *format, ...); 193 int ms_printf(const char *format, ...); 194 int ms_scanf(const char *format, ...); 195 #define _VA_LIST_DEFINED 196 #define __extension__ 197 #endif 198 199 __UINT16_TYPE__ __builtin_bswap16 (__UINT16_TYPE__ x); 200 __UINT32_TYPE__ __builtin_bswap32 (__UINT32_TYPE__ x); 201 __UINT64_TYPE__ __builtin_bswap64 (__UINT64_TYPE__ x); 202 char *__builtin___strcat_chk (char *dest, const char *src, size_t os); 203 char *__builtin___strcpy_chk (char *dest, const char *src, size_t os); 204 char *__builtin___strncpy_chk(char *dest, char *src, size_t n, size_t os); 205 char *__builtin_strchr(const char *s, int c); 206 char *__builtin_strcpy(char *dest, const char *src); 207 double __builtin_copysign ( double x, double y ); 208 double __builtin_copysignl (long double x, long double y ); 209 double __builtin_fabs(double x); 210 double __builtin_huge_val (void); 211 double __builtin_inf (void); 212 double __builtin_nan (const char *str); 213 float __builtin_copysignf ( float x, float y ); 214 float __builtin_fabsf(float x); 215 float __builtin_huge_valf (void); 216 float __builtin_inff (void); 217 float __builtin_nanf (const char *str); 218 int __builtin___snprintf_chk (char *s, size_t maxlen, int flag, size_t os, const char *fmt, ...); 219 int __builtin___sprintf_chk (char *s, int flag, size_t os, const char *fmt, ...); 220 int __builtin___vsnprintf_chk (char *s, size_t maxlen, int flag, size_t os, const char *fmt, __builtin_va_list ap); 221 int __builtin__snprintf_chk(char * str, size_t maxlen, int flag, size_t strlen, const char * format); 222 int __builtin_abs(int j); 223 int __builtin_add_overflow(); 224 int __builtin_clz (unsigned); 225 int __builtin_isunordered(double x, double y); 226 int __builtin_clzl (unsigned long); 227 int __builtin_clzll (unsigned long long); 228 int __builtin_constant_p_impl(int, ...); 229 int __builtin_getentropy(void*, size_t); 230 int __builtin_isnan(double); 231 int __builtin_memcmp(const void *s1, const void *s2, size_t n); 232 int __builtin_mul_overflow(); 233 int __builtin_popcount (unsigned int x); 234 int __builtin_popcountl (unsigned long x); 235 int __builtin_printf(const char *format, ...); 236 int __builtin_snprintf(char *str, size_t size, const char *format, ...); 237 int __builtin_sprintf(char *str, const char *format, ...); 238 int __builtin_strcmp(const char *s1, const char *s2); 239 int __builtin_sub_overflow(); 240 long __builtin_expect (long exp, long c); 241 long double __builtin_fabsl(long double x); 242 long double __builtin_nanl (const char *str); 243 long long __builtin_llabs(long long j); 244 size_t __builtin_object_size (void * ptr, int type); 245 size_t __builtin_strlen(const char *s); 246 void *__builtin___memcpy_chk (void *dest, const void *src, size_t n, size_t os); 247 void *__builtin___memmove_chk (void *dest, const void *src, size_t n, size_t os); 248 void *__builtin___memset_chk (void *dstpp, int c, size_t len, size_t dstlen); 249 void *__builtin_malloc(size_t size); 250 void *__builtin_memcpy(void *dest, const void *src, size_t n); 251 void *__builtin_memset(void *s, int c, size_t n); 252 void *__builtin_mmap(void *addr, size_t length, int prot, int flags, int fd, __INTPTR_TYPE__ offset); 253 void *__ccgo_va_arg(__builtin_va_list ap); 254 void __builtin_abort(void); 255 void __builtin_bzero(void *s, size_t n); 256 void __builtin_exit(int status); 257 void __builtin_free(void *ptr); 258 void __builtin_prefetch (const void *addr, ...); 259 void __builtin_trap (void); 260 void __builtin_unreachable (void); 261 void __ccgo_dmesg(char*, ...); 262 void __ccgo_va_end(__builtin_va_list ap); 263 void __ccgo_va_start(__builtin_va_list ap); 264 265 #define __sync_add_and_fetch(ptr, val) \ 266 __builtin_choose_expr( \ 267 __builtin_types_compatible_p(typeof(*ptr), unsigned), \ 268 __sync_add_and_fetch_uint32(ptr, val), \ 269 __TODO__ \ 270 ) 271 272 #define __sync_fetch_and_add(ptr, val) \ 273 __TODO__ \ 274 275 #define __sync_sub_and_fetch(ptr, val) \ 276 __builtin_choose_expr( \ 277 __builtin_types_compatible_p(typeof(*ptr), unsigned), \ 278 __sync_sub_and_fetch_uint32(ptr, val), \ 279 __TODO__ \ 280 ) 281 282 unsigned __sync_add_and_fetch_uint32(unsigned*, unsigned); 283 unsigned __sync_sub_and_fetch_uint32(unsigned*, unsigned); 284 285 #ifdef __APPLE__ 286 int (*__darwin_check_fd_set_overflow)(int, void *, int); 287 #endif 288 289 ` 290 defaultCrt = "modernc.org/libc" 291 ) 292 293 func origin(skip int) string { 294 pc, fn, fl, _ := runtime.Caller(skip) 295 f := runtime.FuncForPC(pc) 296 var fns string 297 if f != nil { 298 fns = f.Name() 299 if x := strings.LastIndex(fns, "."); x > 0 { 300 fns = fns[x+1:] 301 } 302 } 303 return fmt.Sprintf("%s:%d:%s", fn, fl, fns) 304 } 305 306 func todo(s string, args ...interface{}) string { 307 switch { 308 case s == "": 309 s = fmt.Sprintf(strings.Repeat("%v ", len(args)), args...) 310 default: 311 s = fmt.Sprintf(s, args...) 312 } 313 r := fmt.Sprintf("%s\n\tTODO %s", origin(2), s) //TODOOK 314 fmt.Fprintf(os.Stdout, "%s\n", r) 315 os.Stdout.Sync() 316 return r 317 } 318 319 func trc(s string, args ...interface{}) string { 320 switch { 321 case s == "": 322 s = fmt.Sprintf(strings.Repeat("%v ", len(args)), args...) 323 default: 324 s = fmt.Sprintf(s, args...) 325 } 326 r := fmt.Sprintf("%s: TRC %s", origin(2), s) 327 fmt.Fprintf(os.Stderr, "%s\n", r) 328 os.Stderr.Sync() 329 return r 330 } 331 332 // Task represents a compilation job. 333 type Task struct { 334 D []string // -D 335 I []string // -I 336 U []string // -U 337 ar string // $AR, default "ar" 338 arLookPath string // LookPath(ar) 339 args []string 340 asts []*cc.AST 341 capif string 342 saveConfig string // -save-config 343 saveConfigErr error 344 cc string // $CC, default "gcc" 345 ccLookPath string // LookPath(cc) 346 cdb string // foo.json, use compile DB 347 cfg *cc.Config 348 compiledb string // -compiledb 349 crt string 350 crtImportPath string // -crt-import-path 351 exportDefines string // -export-defines 352 exportEnums string // -export-enums 353 exportExterns string // -export-externs 354 exportFields string // -export-fields 355 exportStructs string // -export-structs 356 exportTypedefs string // -export-typedefs 357 goarch string 358 goos string 359 hide map[string]struct{} // -hide 360 hostConfigCmd string // -host-config-cmd 361 hostConfigOpts string // -host-config-opts 362 hostIncludes []string 363 hostPredefined string 364 hostSysIncludes []string 365 ignoredIncludes string // -ignored-includes 366 ignoredObjects map[string]struct{} // -ignore-object 367 imported []*imported 368 includedFiles map[string]struct{} 369 l []string // -l 370 loadConfig string // --load-config 371 o string // -o 372 out io.Writer 373 pkgName string // -pkgname 374 replaceFdZero string // -replace-fd-zero 375 replaceTclDefaultDoubleRounding string // -replace-tcl-default-double-rounding 376 replaceTclIeeeDoubleRounding string // -replace-tcl-default-double-rounding 377 scriptFn string // -script 378 sources []cc.Source 379 staticLocalsPrefix string // -static-locals-prefix 380 stderr io.Writer 381 stdout io.Writer 382 symSearchOrder []int // >= 0: asts[i], < 0 : imported[-i-1] 383 verboseCompiledb bool // -verbose-compiledb 384 volatiles map[cc.StringID]struct{} // -volatile 385 386 // Path to a binary that will be called instead of executing 387 // Task.Main(). Intended to support TestGenerate in stable vs latest 388 // modes. This is _not_ supposed to be used when the Task instance is 389 // constructed by a ccgo _command_ (ccgo/v3) - it should never set this 390 // field. Only programs importing ccgo/v3/lib that opt-in into this 391 // feature should ever set it. 392 CallOutBinary string 393 394 E bool // -E 395 allErrors bool // -all-errors 396 compiledbValid bool // -compiledb present 397 configSaved bool 398 configured bool // hostPredefined, hostIncludes, hostSysIncludes are valid 399 cover bool // -cover-instrumentation 400 coverC bool // -cover-instrumentation-c 401 defaultUnExport bool // -unexported-by-default 402 errTrace bool // -err-trace 403 exportDefinesValid bool // -export-defines present 404 exportEnumsValid bool // -export-enums present 405 exportExternsValid bool // -export-externs present 406 exportFieldsValid bool // -export-fields present 407 exportStructsValid bool // -export-structs present 408 exportTypedefsValid bool // -export-typedefs present 409 fullPathComments bool // -full-path-comments 410 funcSig bool // -func-sig 411 header bool // -header 412 ignoreUnsupportedAligment bool // -ignore-unsupported-alignment 413 isScripted bool 414 mingw bool 415 noCapi bool // -nocapi 416 nostdinc bool // -nostdinc 417 nostdlib bool // -nostdlib 418 panicStubs bool // -panic-stubs 419 tracePinning bool // -trace-pinning 420 traceTranslationUnits bool // -trace-translation-units 421 verifyStructs bool // -verify-structs 422 version bool // -version 423 watch bool // -watch-instrumentation 424 windows bool // -windows 425 } 426 427 // NewTask returns a newly created Task. 428 func NewTask(args []string, stdout, stderr io.Writer) *Task { 429 if dmesgs { 430 dmesg("%v: %v", origin(1), args) 431 } 432 if stdout == nil { 433 stdout = os.Stdout 434 } 435 if stderr == nil { 436 stderr = os.Stderr 437 } 438 return &Task{ 439 args: args, 440 cfg: &cc.Config{ 441 Config3: cc.Config3{ 442 MaxSourceLine: maxSourceLine, 443 }, 444 DoNotTypecheckAsm: true, 445 EnableAssignmentCompatibilityChecking: true, 446 LongDoubleIsDouble: true, 447 SharedFunctionDefinitions: &cc.SharedFunctionDefinitions{}, 448 }, 449 ar: env("AR", "ar"), 450 cc: env("CC", "gcc"), 451 crt: "libc.", 452 crtImportPath: defaultCrt, 453 goarch: env("TARGET_GOARCH", env("GOARCH", runtime.GOARCH)), 454 goos: env("TARGET_GOOS", env("GOOS", runtime.GOOS)), 455 hide: map[string]struct{}{}, 456 hostConfigCmd: env("CCGO_CPP", ""), 457 pkgName: "main", 458 stderr: stderr, 459 stdout: stdout, 460 volatiles: map[cc.StringID]struct{}{}, 461 } 462 } 463 464 func env(name, deflt string) (r string) { 465 r = deflt 466 if s := os.Getenv(name); s != "" { 467 r = s 468 } 469 return r 470 } 471 472 // Get exported symbols from package having import path 'path'. 473 func (t *Task) capi(path string) (pkgName string, exports map[string]struct{}, err error) { 474 // defer func() { 475 // var a []string 476 // for k := range exports { 477 // a = append(a, k) 478 // } 479 // sort.Strings(a) 480 // trc("%s\n%s", path, strings.Join(a, "\n")) 481 // }() 482 var errModule, errGopath error 483 defer func() { 484 if err != nil { 485 a := []string{err.Error()} 486 if errModule != nil { 487 a = append(a, fmt.Sprintf("module mode error: %s", errModule)) 488 } 489 if errGopath != nil { 490 a = append(a, fmt.Sprintf("gopath mode error: %s", errGopath)) 491 } 492 wd, err2 := os.Getwd() 493 err = fmt.Errorf( 494 "(wd %q, %v): loading C exports from %s (GOPATH=%v GO111MODULE=%v): %v", 495 wd, err2, path, os.Getenv("GOPATH"), os.Getenv("GO111MODULE"), strings.Join(a, "\n\t"), 496 ) 497 } 498 }() 499 500 mod := os.Getenv("GO111MODULE") 501 if mod == "" || mod == "on" { 502 var pkgs []*packages.Package 503 pkgs, errModule = packages.Load( 504 &packages.Config{ 505 Mode: packages.NeedFiles, 506 Env: append(os.Environ(), fmt.Sprintf("GOOS=%s", t.goos), fmt.Sprintf("GOARCH=%s", t.goarch)), 507 }, 508 path, 509 ) 510 switch { 511 case errModule == nil: 512 if len(pkgs) != 1 { 513 errModule = fmt.Errorf("expected one package, loaded %d", len(pkgs)) 514 break 515 } 516 517 pkg := pkgs[0] 518 if len(pkg.Errors) != 0 { 519 var a []string 520 for _, v := range pkg.Errors { 521 a = append(a, v.Error()) 522 } 523 errModule = fmt.Errorf("%s", strings.Join(a, "\n")) 524 break 525 } 526 527 return t.capi2(pkg.GoFiles) 528 } 529 } 530 531 gopath0 := os.Getenv("GOPATH") 532 for _, gopath := range strings.Split(gopath0, string(os.PathListSeparator)) { 533 if gopath == "" || !filepath.IsAbs(gopath) { 534 continue 535 } 536 537 ctx := build.Context{ 538 GOARCH: t.goarch, 539 GOOS: t.goos, 540 GOPATH: gopath, 541 Compiler: "gc", 542 } 543 arg := filepath.Join(gopath, "src", path) 544 pkg, err := ctx.ImportDir(arg, 0) 545 if err != nil { 546 errGopath = err 547 continue 548 } 549 550 for i, v := range pkg.GoFiles { 551 pkg.GoFiles[i] = filepath.Join(gopath, "src", path, v) 552 } 553 return t.capi2(pkg.GoFiles) 554 } 555 return "", nil, fmt.Errorf("cannot load CAPI") 556 } 557 558 func (t *Task) capi2(files []string) (pkgName string, exports map[string]struct{}, err error) { 559 exports = map[string]struct{}{} 560 base := fmt.Sprintf("capi_%s_%s.go", t.goos, t.goarch) 561 var fn string 562 for _, v := range files { 563 if filepath.Base(v) == base { 564 fn = v 565 break 566 } 567 } 568 if fn == "" { 569 return "", nil, fmt.Errorf("file %s not found", base) 570 } 571 572 fset := token.NewFileSet() 573 file, err := parser.ParseFile(fset, fn, nil, 0) 574 if err != nil { 575 return "", nil, err 576 } 577 578 obj, ok := file.Scope.Objects["CAPI"] 579 if !ok { 580 return "", nil, fmt.Errorf("CAPI not declared in %s", fn) 581 } 582 583 switch obj.Kind { 584 case ast.Var: 585 // ok 586 default: 587 return "", nil, fmt.Errorf("unexpected CAPI object kind: %v", obj.Kind) 588 } 589 590 spec, ok := obj.Decl.(*ast.ValueSpec) 591 if !ok { 592 return "", nil, fmt.Errorf("unexpected CAPI object type: %T", obj.Decl) 593 } 594 595 if len(spec.Values) != 1 { 596 return "", nil, fmt.Errorf("expected one CAPI expression, got %v", len(spec.Values)) 597 } 598 599 ast.Inspect(spec.Values[0], func(n ast.Node) bool { 600 if x, ok := n.(*ast.BasicLit); ok { 601 var key string 602 if key, err = strconv.Unquote(x.Value); err != nil { 603 err = fmt.Errorf("invalid CAPI key value: %s", x.Value) 604 return false 605 } 606 607 exports[key] = struct{}{} 608 } 609 return true 610 }) 611 return file.Name.String(), exports, err 612 } 613 614 // Main executes task. 615 func (t *Task) Main() (err error) { 616 if dmesgs { 617 defer func() { 618 if err != nil { 619 // trc("FAIL %p: %q: %v", t, t.args, err) 620 dmesg("%v: returning from Task.Main: %v", origin(1), err) 621 } 622 }() 623 624 } 625 626 defer func() { 627 if t.saveConfigErr != nil && err == nil { 628 err = t.saveConfigErr 629 } 630 }() 631 632 if !t.isScripted && coverExperiment { 633 defer func() { 634 fmt.Fprintf(os.Stderr, "cover report:\n%s\n", coverReport()) 635 }() 636 } 637 if t.CallOutBinary != "" { 638 if dmesgs { 639 dmesg("%v: calling out '%s' instead", origin(1)) 640 } 641 cmd := exec.Command(t.CallOutBinary, t.args[1:]...) 642 out, err := cmd.CombinedOutput() 643 if err != nil { 644 err = fmt.Errorf("%v\n%s", err, out) 645 } 646 return err 647 } 648 649 opts := opt.NewSet() 650 opts.Arg("D", true, func(arg, value string) error { t.D = append(t.D, value); return nil }) 651 opts.Arg("I", true, func(opt, arg string) error { t.I = append(t.I, arg); return nil }) 652 opts.Arg("U", true, func(arg, value string) error { t.U = append(t.U, value); return nil }) 653 opts.Arg("compiledb", false, func(arg, value string) error { t.compiledb = value; t.compiledbValid = true; return opt.Skip(nil) }) 654 opts.Arg("crt-import-path", false, func(arg, value string) error { t.crtImportPath = value; return nil }) 655 opts.Arg("export-defines", false, func(arg, value string) error { t.exportDefines = value; t.exportDefinesValid = true; return nil }) 656 opts.Arg("export-enums", false, func(arg, value string) error { t.exportEnums = value; t.exportEnumsValid = true; return nil }) 657 opts.Arg("export-externs", false, func(arg, value string) error { t.exportExterns = value; t.exportExternsValid = true; return nil }) 658 opts.Arg("export-fields", false, func(arg, value string) error { t.exportFields = value; t.exportFieldsValid = true; return nil }) 659 opts.Arg("export-structs", false, func(arg, value string) error { t.exportStructs = value; t.exportStructsValid = true; return nil }) 660 opts.Arg("export-typedefs", false, func(arg, value string) error { t.exportTypedefs = value; t.exportTypedefsValid = true; return nil }) 661 opts.Arg("host-config-cmd", false, func(arg, value string) error { t.hostConfigCmd = value; return nil }) 662 opts.Arg("host-config-opts", false, func(arg, value string) error { t.hostConfigOpts = value; return nil }) 663 opts.Arg("ignored-includes", false, func(arg, value string) error { t.ignoredIncludes = value; return nil }) 664 opts.Arg("pkgname", false, func(arg, value string) error { t.pkgName = value; return nil }) 665 opts.Arg("replace-fd-zero", false, func(arg, value string) error { t.replaceFdZero = value; return nil }) 666 opts.Arg("replace-tcl-default-double-rounding", false, func(arg, value string) error { t.replaceTclDefaultDoubleRounding = value; return nil }) 667 opts.Arg("replace-tcl-ieee-double-rounding", false, func(arg, value string) error { t.replaceTclIeeeDoubleRounding = value; return nil }) 668 opts.Arg("script", false, func(arg, value string) error { t.scriptFn = value; return nil }) 669 opts.Arg("static-locals-prefix", false, func(arg, value string) error { t.staticLocalsPrefix = value; return nil }) 670 671 opts.Opt("E", func(opt string) error { t.E = true; return nil }) 672 opts.Opt("all-errors", func(opt string) error { t.allErrors = true; return nil }) 673 opts.Opt("cover-instrumentation", func(opt string) error { t.cover = true; return nil }) 674 opts.Opt("cover-instrumentation-c", func(opt string) error { t.coverC = true; return nil }) 675 opts.Opt("err-trace", func(opt string) error { t.errTrace = true; return nil }) 676 opts.Opt("full-path-comments", func(opt string) error { t.fullPathComments = true; return nil }) 677 opts.Opt("func-sig", func(opt string) error { t.funcSig = true; return nil }) 678 opts.Opt("header", func(opt string) error { t.header = true; return nil }) 679 opts.Opt("ignore-unsupported-alignment", func(opt string) error { t.ignoreUnsupportedAligment = true; return nil }) 680 opts.Opt("nocapi", func(opt string) error { t.noCapi = true; return nil }) 681 opts.Opt("nostdinc", func(opt string) error { t.nostdinc = true; return nil }) 682 opts.Opt("panic-stubs", func(opt string) error { t.panicStubs = true; return nil }) 683 opts.Opt("trace-pinning", func(opt string) error { t.tracePinning = true; return nil }) 684 opts.Opt("trace-translation-units", func(opt string) error { t.traceTranslationUnits = true; return nil }) 685 opts.Opt("unexported-by-default", func(opt string) error { t.defaultUnExport = true; return nil }) 686 opts.Opt("verbose-compiledb", func(opt string) error { t.verboseCompiledb = true; return nil }) 687 opts.Opt("verify-structs", func(opt string) error { t.verifyStructs = true; return nil }) 688 opts.Opt("version", func(opt string) error { t.version = true; return nil }) 689 opts.Opt("watch-instrumentation", func(opt string) error { t.watch = true; return nil }) 690 opts.Opt("windows", func(opt string) error { t.windows = true; return nil }) 691 692 opts.Opt("trace-included-files", func(opt string) error { 693 if t.includedFiles == nil { 694 t.includedFiles = map[string]struct{}{} 695 } 696 prev := t.cfg.IncludeFileHandler 697 t.cfg.IncludeFileHandler = func(pos token.Position, pathName string) { 698 if prev != nil { 699 prev(pos, pathName) 700 } 701 if _, ok := t.includedFiles[pathName]; !ok { 702 t.includedFiles[pathName] = struct{}{} 703 fmt.Fprintf(os.Stderr, "#include %s\n", pathName) 704 } 705 } 706 return nil 707 }) 708 opts.Arg("ignore-object", false, func(arg, value string) error { 709 if t.ignoredObjects == nil { 710 t.ignoredObjects = map[string]struct{}{} 711 } 712 t.ignoredObjects[value] = struct{}{} 713 return nil 714 }) 715 opts.Arg("save-config", false, func(arg, value string) error { 716 if value == "" { 717 return nil 718 } 719 720 abs, err := filepath.Abs(value) 721 if err != nil { 722 return err 723 } 724 725 t.saveConfig = abs 726 if t.includedFiles == nil { 727 t.includedFiles = map[string]struct{}{} 728 } 729 prev := t.cfg.IncludeFileHandler 730 t.cfg.IncludeFileHandler = func(pos token.Position, pathName string) { 731 if prev != nil { 732 prev(pos, pathName) 733 } 734 if _, ok := t.includedFiles[pathName]; !ok { 735 t.includedFiles[pathName] = struct{}{} 736 full := filepath.Join(abs, pathName) 737 switch _, err := os.Stat(full); { 738 case err != nil && os.IsNotExist(err): 739 // ok 740 case err != nil: 741 t.saveConfigErr = err 742 return 743 default: 744 return 745 } 746 747 b, err := ioutil.ReadFile(pathName) 748 if err != nil { 749 t.saveConfigErr = err 750 return 751 } 752 753 dir, _ := filepath.Split(full) 754 if err := os.MkdirAll(dir, 0700); err != nil { 755 t.saveConfigErr = err 756 return 757 } 758 759 if err := ioutil.WriteFile(full, b, 0600); err != nil { 760 t.saveConfigErr = err 761 } 762 } 763 } 764 return nil 765 }) 766 opts.Arg("-load-config", false, func(arg, value string) error { 767 if value == "" { 768 return nil 769 } 770 771 abs, err := filepath.Abs(value) 772 if err != nil { 773 return err 774 } 775 776 t.loadConfig = abs 777 return nil 778 }) 779 opts.Arg("volatile", false, func(arg, value string) error { 780 for _, v := range strings.Split(strings.TrimSpace(value), ",") { 781 t.volatiles[cc.String(v)] = struct{}{} 782 } 783 return nil 784 }) 785 opts.Opt("nostdlib", func(opt string) error { 786 t.nostdlib = true 787 t.crt = "" 788 t.crtImportPath = "" 789 return nil 790 }) 791 opts.Arg("hide", false, func(arg, value string) error { 792 value = strings.TrimSpace(value) 793 a := strings.Split(value, ",") 794 for _, v := range a { 795 t.hide[v] = struct{}{} 796 } 797 return nil 798 }) 799 opts.Arg("l", true, func(arg, value string) error { 800 value = strings.TrimSpace(value) 801 a := strings.Split(value, ",") 802 for _, v := range a { 803 t.l = append(t.l, v) 804 t.symSearchOrder = append(t.symSearchOrder, -len(t.l)) 805 } 806 return nil 807 }) 808 opts.Arg("o", false, func(arg, value string) error { 809 if t.o != "" { 810 return fmt.Errorf("multiple argument: -o %s", value) 811 } 812 813 t.o = value 814 return nil 815 }) 816 if err := opts.Parse(t.args[1:], func(arg string) error { 817 if strings.HasPrefix(arg, "-") { 818 return fmt.Errorf("unexpected option: %s", arg) 819 } 820 821 switch filepath.Ext(arg) { 822 case ".h": 823 t.symSearchOrder = append(t.symSearchOrder, len(t.sources)) 824 t.sources = append(t.sources, cc.Source{Name: arg}) 825 case ".c": 826 t.symSearchOrder = append(t.symSearchOrder, len(t.sources)) 827 t.sources = append(t.sources, cc.Source{Name: arg, DoNotCache: true}) 828 case ".json": 829 t.cdb = arg 830 return opt.Skip(nil) 831 default: 832 return fmt.Errorf("unexpected file type: %s", arg) 833 } 834 835 return nil 836 }); err != nil { 837 switch x := err.(type) { 838 case opt.Skip: 839 switch { 840 case t.compiledbValid: // -compiledb foo.json, create DB 841 cmd := []string(x)[1:] 842 if len(cmd) == 0 { 843 return fmt.Errorf("missing command after -compiledb <file>") 844 } 845 846 return t.createCompileDB(cmd) 847 case t.cdb != "": // foo.json ..., use DB 848 if err := t.configure(); err != nil { 849 return err 850 } 851 852 return t.useCompileDB(t.cdb, x) 853 } 854 855 return err 856 default: 857 return err 858 } 859 } 860 861 if t.version { 862 gobin, err := exec.LookPath("go") 863 var b []byte 864 if err == nil { 865 var bin string 866 bin, err = exec.LookPath(os.Args[0]) 867 if err == nil { 868 b, err = exec.Command(gobin, "version", "-m", bin).CombinedOutput() 869 } 870 } 871 if err == nil { 872 fmt.Fprintf(t.stdout, "%s", b) 873 return nil 874 } 875 876 fmt.Fprintf(t.stdout, "%s\n", Version) 877 return nil 878 } 879 880 if t.scriptFn != "" { 881 return t.scriptBuild(t.scriptFn) 882 } 883 884 if len(t.sources) == 0 { 885 return fmt.Errorf("no input files specified") 886 } 887 888 if t.crtImportPath != "" { 889 t.l = append(t.l, t.crtImportPath) 890 t.symSearchOrder = append(t.symSearchOrder, -len(t.l)) 891 m := map[string]struct{}{} 892 for _, v := range t.l { 893 v = strings.TrimSpace(v) 894 if _, ok := m[v]; !ok { 895 t.imported = append(t.imported, &imported{path: v}) 896 m[v] = struct{}{} 897 } 898 } 899 t.imported[len(t.imported)-1].used = true // crt is always imported 900 } 901 902 if err := t.configure(); err != nil { 903 return err 904 } 905 906 abi, err := cc.NewABI(t.goos, t.goarch) 907 if err != nil { 908 return err 909 } 910 abi.Types[cc.LongDouble] = abi.Types[cc.Double] 911 912 var re *regexp.Regexp 913 if t.ignoredIncludes != "" { 914 if re, err = regexp.Compile(t.ignoredIncludes); err != nil { 915 return err 916 } 917 } 918 919 t.cfg.ABI = abi 920 t.cfg.ReplaceMacroFdZero = t.replaceFdZero 921 t.cfg.ReplaceMacroTclDefaultDoubleRounding = t.replaceTclDefaultDoubleRounding 922 t.cfg.ReplaceMacroTclIeeeDoubleRounding = t.replaceTclIeeeDoubleRounding 923 t.cfg.Config3.IgnoreInclude = re 924 t.cfg.Config3.NoFieldAndBitfieldOverlap = true 925 t.cfg.Config3.PreserveWhiteSpace = t.saveConfig == "" 926 t.cfg.Config3.UnsignedEnums = true 927 928 if t.mingw = detectMingw(t.hostPredefined); t.mingw { 929 t.windows = true 930 } 931 if t.nostdinc { 932 t.hostIncludes = nil 933 t.hostSysIncludes = nil 934 } 935 var sources []cc.Source 936 if t.hostPredefined != "" { 937 sources = append(sources, cc.Source{Name: "<predefined>", Value: t.hostPredefined}) 938 } 939 sources = append(sources, cc.Source{Name: "<builtin>", Value: builtin}) 940 if len(t.D) != 0 { 941 var a []string 942 for _, v := range t.D { 943 if i := strings.IndexByte(v, '='); i > 0 { 944 a = append(a, fmt.Sprintf("#define %s %s", v[:i], v[i+1:])) 945 continue 946 } 947 948 a = append(a, fmt.Sprintf("#define %s 1", v)) 949 } 950 a = append(a, "\n") 951 sources = append(sources, cc.Source{Name: "<defines>", Value: strings.Join(a, "\n"), DoNotCache: true}) 952 } 953 if len(t.U) != 0 { 954 var a []string 955 for _, v := range t.U { 956 a = append(a, fmt.Sprintf("#undef %s", v)) 957 } 958 a = append(a, "\n") 959 sources = append(sources, cc.Source{Name: "<undefines>", Value: strings.Join(a, "\n"), DoNotCache: true}) 960 } 961 962 // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/c99.html 963 // 964 // Headers whose names are enclosed in double-quotes ( "" ) shall be 965 // searched for first in the directory of the file with the #include 966 // line, then in directories named in -I options, and last in the usual 967 // places 968 includePaths := append([]string{"@"}, t.I...) 969 includePaths = append(includePaths, t.hostIncludes...) 970 includePaths = append(includePaths, t.hostSysIncludes...) 971 // For headers whose names are enclosed in angle brackets ( "<>" ), the 972 // header shall be searched for only in directories named in -I options 973 // and then in the usual places. 974 sysIncludePaths := append(t.I, t.hostSysIncludes...) 975 if t.traceTranslationUnits { 976 fmt.Printf("target: %s/%s\n", t.goos, t.goarch) 977 if t.hostConfigCmd != "" { 978 fmt.Printf("host config cmd: %s\n", t.hostConfigCmd) 979 } 980 } 981 for i, v := range t.sources { 982 tuSources := append(sources, v) 983 out := t.stdout 984 if t.saveConfig != "" { 985 out = io.Discard 986 t.E = true 987 } 988 if t.E { 989 t.cfg.PreprocessOnly = true 990 if err := cc.Preprocess(t.cfg, includePaths, sysIncludePaths, tuSources, out); err != nil { 991 return err 992 } 993 memGuard(i, t.isScripted) 994 continue 995 } 996 997 var t0 time.Time 998 if t.traceTranslationUnits { 999 fmt.Printf("C front end %d/%d: %s ... ", i+1, len(t.sources), v.Name) 1000 t0 = time.Now() 1001 } 1002 ast, err := cc.Translate(t.cfg, includePaths, sysIncludePaths, tuSources) 1003 if err != nil { 1004 return err 1005 } 1006 1007 if t.traceTranslationUnits { 1008 fmt.Println(time.Since(t0)) 1009 } 1010 t.asts = append(t.asts, ast) 1011 memGuard(i, t.isScripted) 1012 } 1013 if t.E || t.isScripted { 1014 return nil 1015 } 1016 1017 return t.link() 1018 } 1019 1020 func (t *Task) configure() (err error) { 1021 if t.configured { 1022 return nil 1023 } 1024 1025 type jsonConfig struct { 1026 Predefined string 1027 IncludePaths []string 1028 SysIncludePaths []string 1029 OS string 1030 Arch string 1031 } 1032 1033 t.configured = true 1034 if t.loadConfig != "" { 1035 path := filepath.Join(t.loadConfig, "config.json") 1036 // trc("%p: LOAD_CONFIG(%s)", t, path) 1037 b, err := ioutil.ReadFile(path) 1038 if err != nil { 1039 return err 1040 } 1041 1042 loadConfig := &jsonConfig{} 1043 if err := json.Unmarshal(b, loadConfig); err != nil { 1044 return err 1045 } 1046 1047 t.goos = loadConfig.OS 1048 t.goarch = loadConfig.Arch 1049 for _, v := range loadConfig.IncludePaths { 1050 t.hostIncludes = append(t.hostIncludes, filepath.Join(t.loadConfig, v)) 1051 } 1052 for _, v := range loadConfig.SysIncludePaths { 1053 t.hostSysIncludes = append(t.hostSysIncludes, filepath.Join(t.loadConfig, v)) 1054 } 1055 t.hostPredefined = loadConfig.Predefined 1056 return nil 1057 } 1058 1059 hostConfigOpts := strings.Split(t.hostConfigOpts, ",") 1060 if t.hostConfigOpts == "" { 1061 hostConfigOpts = nil 1062 } 1063 if t.hostPredefined, t.hostIncludes, t.hostSysIncludes, err = cc.HostConfig(t.hostConfigCmd, hostConfigOpts...); err != nil { 1064 return err 1065 } 1066 1067 if t.saveConfig != "" && !t.configSaved { 1068 t.configSaved = true 1069 // trc("%p: SAVE_CONFIG(%s)", t, t.saveConfig) 1070 cfg := &jsonConfig{ 1071 Predefined: t.hostPredefined, 1072 IncludePaths: t.hostIncludes, 1073 SysIncludePaths: t.hostSysIncludes, 1074 OS: t.goos, 1075 Arch: t.goarch, 1076 } 1077 b, err := json.Marshal(cfg) 1078 if err != nil { 1079 return err 1080 } 1081 1082 full := filepath.Join(t.saveConfig, "config.json") 1083 if err := os.MkdirAll(t.saveConfig, 0700); err != nil { 1084 return err 1085 } 1086 1087 if err := ioutil.WriteFile(full, b, 0600); err != nil { 1088 return err 1089 } 1090 } 1091 1092 return nil 1093 } 1094 1095 func (t *Task) setLookPaths() (err error) { 1096 if t.ccLookPath, err = exec.LookPath(t.cc); err != nil { 1097 return err 1098 } 1099 1100 t.arLookPath, err = exec.LookPath(t.ar) 1101 return err 1102 } 1103 1104 func (t *Task) link() (err error) { 1105 if len(t.asts) == 0 { 1106 return fmt.Errorf("no objects to link") 1107 } 1108 1109 if t.o == "" { 1110 t.o = fmt.Sprintf("a_%s_%s.go", t.goos, t.goarch) 1111 } 1112 dir := filepath.Dir(t.o) 1113 t.capif = filepath.Join(dir, fmt.Sprintf("capi_%s_%s.go", t.goos, t.goarch)) 1114 f, err2 := os.Create(t.o) 1115 if err2 != nil { 1116 return err2 1117 } 1118 1119 defer func() { 1120 if e := f.Close(); e != nil && err == nil { 1121 err = e 1122 return 1123 } 1124 1125 if out, e := exec.Command("gofmt", "-r", "(x) -> x", "-l", "-s", "-w", t.o).CombinedOutput(); e != nil && err == nil { 1126 err = fmt.Errorf(strings.Join([]string{string(out), e.Error()}, ": ")) 1127 } 1128 if out, e := exec.Command("gofmt", "-l", "-s", "-w", t.o).CombinedOutput(); e != nil && err == nil { 1129 err = fmt.Errorf(strings.Join([]string{string(out), e.Error()}, ": ")) 1130 } 1131 }() 1132 1133 w := bufio.NewWriter(f) 1134 1135 defer func() { 1136 if e := w.Flush(); e != nil && err == nil { 1137 err = e 1138 } 1139 }() 1140 1141 t.out = w 1142 p, err := newProject(t) 1143 if err != nil { 1144 return err 1145 } 1146 1147 return p.main() 1148 } 1149 1150 func (t *Task) scriptBuild(fn string) error { 1151 f, err := os.Open(fn) 1152 if err != nil { 1153 return err 1154 } 1155 1156 defer f.Close() 1157 1158 r := csv.NewReader(f) 1159 r.Comment = '#' 1160 r.FieldsPerRecord = -1 1161 r.TrimLeadingSpace = true 1162 script, err := r.ReadAll() 1163 if err != nil { 1164 return err 1165 } 1166 1167 return t.scriptBuild2(script) 1168 } 1169 1170 func (t *Task) scriptBuild2(script [][]string) error { 1171 var ldir string 1172 ccgo := []string{t.args[0]} 1173 for i, line := range script { 1174 dir := line[0] 1175 args := line[1:] 1176 for _, v := range args { 1177 if strings.HasSuffix(v, ".c") || strings.HasSuffix(v, ".h") { 1178 v = filepath.Join(dir, v) 1179 t.symSearchOrder = append(t.symSearchOrder, len(t.sources)) 1180 t.sources = append(t.sources, cc.Source{Name: v}) 1181 } 1182 } 1183 cmd := append(ccgo, args...) 1184 if t.traceTranslationUnits { 1185 if dir != ldir { 1186 fmt.Println(dir) 1187 ldir = dir 1188 } 1189 fmt.Printf("%s\n", cmd) 1190 } 1191 t2 := NewTask(append(ccgo, args...), t.stdout, t.stderr) 1192 t2.cfg.IncludeFileHandler = t.cfg.IncludeFileHandler 1193 t2.cfg.SharedFunctionDefinitions = t.cfg.SharedFunctionDefinitions 1194 t2.configSaved = t.configSaved 1195 t2.configured = t.configured 1196 t2.hostIncludes = t.hostIncludes 1197 t2.hostPredefined = t.hostPredefined 1198 t2.hostSysIncludes = t.hostSysIncludes 1199 t2.includedFiles = t.includedFiles 1200 t2.isScripted = true 1201 t2.loadConfig = t.loadConfig 1202 t2.replaceFdZero = t.replaceFdZero 1203 t2.replaceTclDefaultDoubleRounding = t.replaceTclDefaultDoubleRounding 1204 t2.replaceTclIeeeDoubleRounding = t.replaceTclIeeeDoubleRounding 1205 t2.saveConfig = t.saveConfig 1206 if err := inDir(dir, t2.Main); err != nil { 1207 return err 1208 } 1209 1210 t.asts = append(t.asts, t2.asts...) 1211 if i == 0 { 1212 t.cfg = t2.cfg 1213 } 1214 } 1215 if t.crtImportPath != "" { 1216 t.l = append(t.l, t.crtImportPath) 1217 t.symSearchOrder = append(t.symSearchOrder, -len(t.l)) 1218 m := map[string]struct{}{} 1219 for _, v := range t.l { 1220 v = strings.TrimSpace(v) 1221 if _, ok := m[v]; !ok { 1222 t.imported = append(t.imported, &imported{path: v}) 1223 m[v] = struct{}{} 1224 } 1225 } 1226 t.imported[len(t.imported)-1].used = true // crt is always imported 1227 } 1228 if t.saveConfig != "" { 1229 return nil 1230 } 1231 1232 return t.link() 1233 } 1234 1235 type cdb struct { 1236 items []*cdbItem 1237 outputIndex map[string][]*cdbItem 1238 } 1239 1240 func (db *cdb) find(obj map[string]*cdbItem, nm string, ver, seqLimit int, path []string, cc, ar string, ignored map[string]struct{}) error { 1241 // trc("%v: nm %q ver %v seqLimit %v path %q cc %q ar %q", origin(1), nm, ver, seqLimit, path, cc, ar) 1242 var item *cdbItem 1243 var k string 1244 switch { 1245 case ver < 0: 1246 // find highest ver with .seq < seqLimit 1247 for i, v := range db.outputIndex[nm] { 1248 if v.seq >= seqLimit { 1249 break 1250 } 1251 1252 item = v 1253 ver = i 1254 } 1255 if item == nil { 1256 ver = -1 1257 for _, v := range db.items { 1258 if seqLimit >= 0 && v.seq >= seqLimit { 1259 break 1260 } 1261 1262 if filepath.Base(v.Output) == filepath.Base(nm) { 1263 item = v 1264 ver = v.ver 1265 break 1266 } 1267 } 1268 } 1269 1270 k = fmt.Sprintf("%s#%d", nm, ver) 1271 default: 1272 // match ver exactly 1273 k = fmt.Sprintf("%s#%d", nm, ver) 1274 if obj[k] != nil { 1275 return nil 1276 } 1277 1278 items := db.outputIndex[nm] 1279 switch { 1280 case ver < len(items): 1281 panic(todo("", nm, ver, seqLimit)) 1282 default: 1283 n := -1 1284 for _, v := range db.items { 1285 if seqLimit >= 0 && v.seq >= seqLimit { 1286 break 1287 } 1288 1289 if filepath.Base(v.Output) == filepath.Base(nm) { 1290 n++ 1291 if n == ver { 1292 item = v 1293 break 1294 } 1295 } 1296 } 1297 } 1298 } 1299 if item == nil { 1300 for k := range ignored { 1301 if k == nm || strings.HasSuffix(nm, k) { 1302 return nil 1303 } 1304 } 1305 1306 return fmt.Errorf("not found in compile DB: %s (max seq %d), path %v", k, seqLimit, path) 1307 } 1308 1309 if obj[k] != nil { 1310 return nil 1311 } 1312 1313 obj[k] = item 1314 var errs []string 1315 for _, v := range item.sources(cc, ar) { 1316 if err := db.find(obj, v, -1, item.seq, append(path, nm), cc, ar, ignored); err != nil { 1317 errs = append(errs, err.Error()) 1318 } 1319 } 1320 if len(errs) != 0 { 1321 sort.Strings(errs) 1322 w := 0 1323 for _, v := range errs { 1324 if w == 0 || w > 0 && v != errs[w-1] { 1325 errs[w] = v 1326 w++ 1327 } 1328 } 1329 errs = errs[:w] 1330 return fmt.Errorf("%s", strings.Join(errs, "\n")) 1331 } 1332 1333 return nil 1334 } 1335 1336 func suffixNum(s string, dflt int) (string, int) { 1337 x := strings.LastIndexByte(s, '#') 1338 if x < 0 { 1339 return s, dflt 1340 } 1341 1342 // foo#42 1343 // 012345 1344 // x == 3 1345 num := s[x+1:] 1346 n, err := strconv.ParseUint(num, 10, 32) 1347 if err != nil { 1348 return s, dflt 1349 } 1350 1351 return s[:x], int(n) 1352 } 1353 1354 func (t *Task) useCompileDB(fn string, args []string) error { 1355 if err := t.setLookPaths(); err != nil { 1356 return err 1357 } 1358 1359 var cdb cdb 1360 f, err := os.Open(fn) 1361 if err != nil { 1362 return err 1363 } 1364 1365 de := json.NewDecoder(f) 1366 err = de.Decode(&cdb.items) 1367 f.Close() 1368 if err != nil { 1369 return err 1370 } 1371 1372 cdb.outputIndex = map[string][]*cdbItem{} 1373 for i, v := range cdb.items { 1374 v.seq = i 1375 if len(v.Arguments) == 0 { 1376 if len(v.Command) == 0 { 1377 return fmt.Errorf("either arguments or command is required: %+v", v) 1378 } 1379 1380 if v.Arguments, err = shellquote.Split(v.Command); err != nil { 1381 return err 1382 } 1383 } 1384 1385 k := v.output(t.ccLookPath, t.arLookPath) 1386 a := cdb.outputIndex[k] 1387 v.ver = len(a) 1388 cdb.outputIndex[k] = append(a, v) 1389 } 1390 obj := map[string]*cdbItem{} 1391 notFound := false 1392 for _, v := range args { 1393 v, ver := suffixNum(v, 0) 1394 if err := cdb.find(obj, v, ver, -1, nil, t.ccLookPath, t.arLookPath, t.ignoredObjects); err != nil { 1395 notFound = true 1396 fmt.Fprintln(os.Stderr, err) 1397 } 1398 } 1399 if notFound { 1400 var a []string 1401 for k, v := range cdb.outputIndex { 1402 for _, w := range v { 1403 a = append(a, fmt.Sprintf("%5d %s", w.seq, k)) 1404 } 1405 } 1406 sort.Strings(a) 1407 fmt.Fprintf(os.Stderr, "compile DB index:\n\t%s\n", strings.Join(a, "\n\t")) 1408 } 1409 1410 var a []string 1411 for k := range obj { 1412 a = append(a, k) 1413 } 1414 sort.Strings(a) 1415 return t.cdbBuild(obj, a) 1416 } 1417 1418 func (t *Task) cdbBuild(obj map[string]*cdbItem, list []string) error { 1419 var script [][]string 1420 for _, nm := range list { 1421 it := obj[nm] 1422 if !strings.HasSuffix(it.Output, ".o") || it.Arguments[0] != t.cc { 1423 continue 1424 } 1425 1426 args, err := it.ccgoArgs(t.cc) 1427 if err != nil { 1428 return err 1429 } 1430 1431 for _, v := range t.D { 1432 args = append(args, "-D"+v) 1433 } 1434 for _, v := range t.U { 1435 args = append(args, "-U"+v) 1436 } 1437 1438 line := append([]string{it.Directory}, args...) 1439 script = append(script, line) 1440 } 1441 return t.scriptBuild2(script) 1442 } 1443 1444 func (t *Task) createCompileDB(command []string) (rerr error) { 1445 if err := t.setLookPaths(); err != nil { 1446 return err 1447 } 1448 1449 cwd, err := os.Getwd() 1450 if err != nil { 1451 return err 1452 } 1453 1454 f, err := os.Create(t.compiledb) 1455 if err != nil { 1456 return err 1457 } 1458 1459 defer func() { 1460 if err := f.Close(); err != nil && rerr == nil { 1461 rerr = err 1462 } 1463 }() 1464 1465 cwr := newCDBWriter(f) 1466 1467 defer func() { 1468 if err := cwr.finish(); err != nil && rerr == nil { 1469 rerr = err 1470 } 1471 }() 1472 1473 var cmd *exec.Cmd 1474 var parser func(s string) ([]string, error) 1475 out: 1476 switch t.goos { 1477 case "darwin", "freebsd", "netbsd": 1478 switch command[0] { 1479 case "make", "gmake": 1480 // ok 1481 default: 1482 return fmt.Errorf("usupported build command: %s", command[0]) 1483 } 1484 1485 sh, err := exec.LookPath("sh") 1486 if err != nil { 1487 return err 1488 } 1489 1490 command = append([]string{sh, "-c"}, join(" ", command[0], "SHELL='sh -x'", command[1:])) 1491 cmd = exec.Command(command[0], command[1:]...) 1492 parser = makeXParser 1493 case "openbsd": 1494 switch command[0] { 1495 case "make", "gmake": 1496 // ok 1497 default: 1498 return fmt.Errorf("usupported build command: %s", command[0]) 1499 } 1500 1501 sh, err := exec.LookPath("sh") 1502 if err != nil { 1503 return err 1504 } 1505 1506 command = append([]string{sh, "-c"}, join(" ", command[0], "SHELL='sh -x'", command[1:])) 1507 cmd = exec.Command(command[0], command[1:]...) 1508 parser = makeXParser2 1509 case "windows": 1510 if command[0] != "make" && command[0] != "make.exe" { 1511 return fmt.Errorf("usupported build command: %s", command[0]) 1512 } 1513 1514 switch s := runtime.GOOS; s { 1515 case "windows": 1516 argv := append([]string{"-d"}, command[1:]...) 1517 if !strings.HasSuffix(command[0], ".exe") { 1518 command[0] += ".exe" 1519 } 1520 cmd = exec.Command(command[0], argv...) 1521 parser = makeDParser 1522 break out 1523 case "linux": 1524 // ok 1525 default: 1526 return fmt.Errorf("usupported cross compile host: %s", s) 1527 } 1528 1529 fallthrough 1530 default: 1531 strace, err := exec.LookPath("strace") 1532 if err != nil { 1533 return err 1534 } 1535 1536 argv := append([]string{"-f", "-s1000000", "-e", "trace=execve"}, command...) 1537 cmd = exec.Command(strace, argv...) 1538 parser = straceParser 1539 } 1540 cmd.Env = append(os.Environ(), "LC_ALL=C") 1541 cw := t.newCdbMakeWriter(cwr, cwd, parser) 1542 switch { 1543 case t.verboseCompiledb: 1544 cmd.Stdout = io.MultiWriter(cw, os.Stdout) 1545 default: 1546 cmd.Stdout = cw 1547 } 1548 cmd.Stderr = cmd.Stdout 1549 if dmesgs { 1550 dmesg("%v: %v", origin(1), cmd.Args) 1551 } 1552 if err := cmd.Run(); err != nil { 1553 if dmesgs { 1554 dmesg("%v: cmd.Run: %v", origin(1), err) 1555 } 1556 return err 1557 } 1558 1559 return cw.err 1560 } 1561 1562 func makeDParser(s string) ([]string, error) { 1563 const prefix = "CreateProcess(" 1564 if !strings.HasPrefix(s, prefix) { 1565 return nil, nil 1566 } 1567 1568 // s: `CreateProcess(C:\Program Files\CodeBlocks\MinGW\bin\gcc.exe,gcc -O3 -Wall -c -o compress.o compress.c,...)` 1569 s = s[len(prefix):] 1570 // s: `C:\Program Files\CodeBlocks\MinGW\bin\gcc.exe,gcc -O3 -Wall -c -o compress.o compress.c,...)` 1571 x := strings.IndexByte(s, ',') 1572 if x < 0 { 1573 return nil, nil 1574 } 1575 1576 cmd := s[:x] 1577 // cmd: `C:\Program Files\CodeBlocks\MinGW\bin\gcc.exe` 1578 1579 s = s[x+1:] 1580 // s: `gcc -O3 -Wall -c -o compress.o compress.c,...)` 1581 if x = strings.LastIndexByte(s, ','); x < 0 { 1582 return nil, nil 1583 } 1584 1585 s = s[:x] 1586 // s: `gcc -O3 -Wall -c -o compress.o compress.c` 1587 a, err := shellquote.Split(strings.TrimSpace(s)) 1588 if err != nil || len(a) == 0 { 1589 return nil, err 1590 } 1591 1592 return append([]string{cmd}, a[1:]...), nil 1593 } 1594 1595 func isCreateArchive(s string) bool { 1596 // ar modifiers may be in any order so sort characters in s before checking. 1597 // This turns eg `rc` into `cr`. 1598 b := []byte(s) 1599 sort.Slice(b, func(i, j int) bool { return b[i] < b[j] }) 1600 switch string(b) { 1601 case "cq", "cr", "crs", "cru", "r": 1602 return true 1603 } 1604 return false 1605 } 1606 1607 func hasPlusPrefix(s string) (n int, r string) { 1608 for strings.HasPrefix(s, "+") { 1609 n++ 1610 s = s[1:] 1611 } 1612 return n, s 1613 } 1614 1615 func makeXParser(s string) (r []string, err error) { 1616 switch { 1617 case strings.HasPrefix(s, "libtool: link: ar "): 1618 s = s[len("libtool: link:"):] 1619 case strings.HasPrefix(s, "libtool: compile: "): 1620 s = s[len("libtool: compile:"):] 1621 for strings.HasPrefix(s, " ") { 1622 s = s[1:] 1623 } 1624 default: 1625 var n int 1626 if n, s = hasPlusPrefix(s); n == 0 { 1627 return nil, nil 1628 } 1629 } 1630 1631 if !strings.HasPrefix(s, " ") { 1632 return nil, nil 1633 } 1634 1635 s = s[1:] 1636 if dmesgs { 1637 dmesg("%v: source line `%s`, caller %v:", origin(1), s, origin(2)) 1638 } 1639 r, err = shellquote.Split(s) 1640 if dmesgs { 1641 dmesg("%v: shellquote.Split -> %v %[2]q, %v", origin(1), r, err) 1642 } 1643 if err != nil { 1644 if strings.Contains(err.Error(), "Unterminated single-quoted string") { 1645 return nil, nil // ignore 1646 } 1647 } 1648 if len(r) != 0 && filepath.Base(r[0]) == "libtool" { 1649 r[0] = "libtool" 1650 } 1651 return r, err 1652 } 1653 1654 func makeXParser2(s string) (r []string, err error) { 1655 s = strings.TrimSpace(s) 1656 switch { 1657 case strings.HasPrefix(s, "libtool: link: ar "): 1658 s = s[len("libtool: link:"):] 1659 case strings.HasPrefix(s, "libtool: compile: "): 1660 s = s[len("libtool: compile:"):] 1661 for strings.HasPrefix(s, " ") { 1662 s = s[1:] 1663 } 1664 default: 1665 var n int 1666 if n, s = hasPlusPrefix(s); n != 0 { 1667 return nil, nil 1668 } 1669 } 1670 1671 if dmesgs { 1672 dmesg("%v: source line `%s`, caller %v:", origin(1), s, origin(2)) 1673 } 1674 r, err = shellquote.Split(s) 1675 if dmesgs { 1676 dmesg("%v: shellquote.Split -> %v %[2]q, %v", origin(1), r, err) 1677 } 1678 if err != nil { 1679 if strings.Contains(err.Error(), "Unterminated single-quoted string") { 1680 return nil, nil // ignore 1681 } 1682 } 1683 if len(r) != 0 && filepath.Base(r[0]) == "libtool" { 1684 r[0] = "libtool" 1685 } 1686 return r, err 1687 } 1688 1689 func straceParser(s string) ([]string, error) { 1690 prefix := "execve(" 1691 if strings.HasPrefix(s, "[pid ") { 1692 s = strings.TrimSpace(s[strings.IndexByte(s, ']')+1:]) 1693 } 1694 if !strings.HasPrefix(s, prefix) || !strings.HasSuffix(s, ") = 0") { 1695 return nil, nil 1696 } 1697 1698 // s: `execve("/usr/bin/ar", ["ar", "cr", "libtcl8.6.a", "regcomp.o", ... "bn_s_mp_sqr.o", "bn_s_mp_sub.o"], 0x55e6bbf49648 /* 60 vars */) = 0` 1699 s = s[len(prefix):] 1700 // s: `"/usr/bin/ar", ["ar", "cr", "libtcl8.6.a", "regcomp.o", ... "bn_s_mp_sqr.o", "bn_s_mp_sub.o"], 0x55e6bbf49648 /* 60 vars */) = 0` 1701 a := strings.SplitN(s, ", [", 2) 1702 // a[0]: `"/usr/bin/ar"`, a[1]: `"ar", "cr", "libtcl8.6.a", "regcomp.o", ... "bn_s_mp_sqr.o", "bn_s_mp_sub.o"], 0x55e6bbf49648 /* 60 vars */) = 0` 1703 args := a[1] 1704 // args: `"ar", "cr", "libtcl8.6.a", "regcomp.o", ... "bn_s_mp_sqr.o", "bn_s_mp_sub.o"], 0x55e6bbf49648 /* 60 vars */) = 0` 1705 args = args[:strings.LastIndex(args, "], ")] 1706 // args: `"ar", "cr", "libtcl8.6.a", "regcomp.o", ... "bn_s_mp_sqr.o", "bn_s_mp_sub.o"` 1707 argv, err := shellquote.Split(args) 1708 if err != nil { 1709 return nil, err 1710 } 1711 1712 words, err := shellquote.Split(a[0]) 1713 if err != nil { 1714 return nil, err 1715 } 1716 1717 argv[0] = words[0] 1718 for i, v := range argv { 1719 if strings.HasSuffix(v, ",") { 1720 v = v[:len(v)-1] 1721 } 1722 if v2, err := strconv.Unquote(`"` + v + `"`); err == nil { 1723 v = v2 1724 } 1725 argv[i] = v 1726 } 1727 1728 return argv, nil 1729 } 1730 1731 type cdbItem struct { 1732 Arguments []string `json:"arguments"` 1733 Command string `json:"command,omitempty"` 1734 Directory string `json:"directory"` 1735 File string `json:"file"` 1736 Output string `json:"output,omitempty"` 1737 1738 seq int 1739 ver int 1740 } 1741 1742 func (it *cdbItem) cmpString() string { return fmt.Sprint(*it) } 1743 1744 func (it *cdbItem) ccgoArgs(cc string) (r []string, err error) { 1745 switch it.Arguments[0] { 1746 case cc: 1747 set := opt.NewSet() 1748 set.Arg("D", true, func(opt, arg string) error { r = append(r, "-D"+arg); return nil }) 1749 set.Arg("I", true, func(opt, arg string) error { r = append(r, "-I"+arg); return nil }) 1750 set.Arg("MF", true, func(opt, arg string) error { return nil }) 1751 set.Arg("MT", true, func(opt, arg string) error { return nil }) 1752 set.Arg("O", true, func(opt, arg string) error { return nil }) 1753 set.Arg("U", true, func(opt, arg string) error { r = append(r, "-U"+arg); return nil }) 1754 set.Arg("o", true, func(opt, arg string) error { return nil }) 1755 set.Arg("std", true, func(opt, arg string) error { return nil }) 1756 set.Opt("MD", func(opt string) error { return nil }) 1757 set.Opt("MMD", func(opt string) error { return nil }) 1758 set.Opt("MP", func(opt string) error { return nil }) 1759 set.Opt("ansi", func(opt string) error { return nil }) 1760 set.Opt("c", func(opt string) error { return nil }) 1761 set.Opt("g", func(opt string) error { return nil }) 1762 set.Opt("pedantic", func(opt string) error { return nil }) 1763 set.Opt("pipe", func(opt string) error { return nil }) 1764 set.Opt("pthread", func(opt string) error { return nil }) 1765 set.Opt("s", func(opt string) error { return nil }) 1766 set.Opt("w", func(opt string) error { return nil }) 1767 if err := set.Parse(it.Arguments[1:], func(arg string) error { 1768 switch { 1769 case strings.HasSuffix(arg, ".c"): 1770 r = append(r, arg) 1771 case 1772 1773 strings.HasPrefix(arg, "-W"), 1774 strings.HasPrefix(arg, "-f"), 1775 strings.HasPrefix(arg, "-m"): 1776 1777 // nop 1778 case strings.HasPrefix(arg, ">"): 1779 return opt.Skip(nil) 1780 default: 1781 return fmt.Errorf("unknown/unsupported CC option: %s", arg) 1782 } 1783 1784 return nil 1785 }); err != nil { 1786 switch err.(type) { 1787 case opt.Skip: 1788 // ok 1789 default: 1790 return nil, err 1791 } 1792 } 1793 1794 return r, nil 1795 default: 1796 return nil, fmt.Errorf("command not supported: %q", it.Arguments[0]) 1797 } 1798 } 1799 1800 func (it *cdbItem) output(cc, ar string) (r string) { 1801 if it.Output != "" { 1802 return it.Output 1803 } 1804 1805 if len(it.Arguments) == 0 { 1806 return "" 1807 } 1808 1809 switch it.Arguments[0] { 1810 case cc: 1811 for i, v := range it.Arguments { 1812 if v == "-o" && i < len(it.Arguments)-1 { 1813 it.Output = filepath.Join(it.Directory, it.Arguments[i+1]) 1814 break 1815 } 1816 } 1817 if it.Output == "" && strings.HasSuffix(it.File, ".c") { 1818 for _, v := range it.Arguments { 1819 if v == "-c" { 1820 bn := filepath.Base(it.File) 1821 it.Output = filepath.Join(it.Directory, bn[:len(bn)-2]+".o") 1822 break 1823 } 1824 } 1825 } 1826 case ar: 1827 if isCreateArchive(it.Arguments[1]) { 1828 it.Output = filepath.Join(it.Directory, it.Arguments[2]) 1829 } 1830 case "libtool": 1831 for i, v := range it.Arguments { 1832 if v == "-o" && i < len(it.Arguments)-1 { 1833 it.Output = filepath.Join(it.Directory, it.Arguments[i+1]) 1834 } 1835 } 1836 } 1837 return it.Output 1838 } 1839 1840 func (it *cdbItem) sources(cc, ar string) (r []string) { 1841 if len(it.Arguments) == 0 { 1842 return nil 1843 } 1844 1845 switch arg0 := it.Arguments[0]; arg0 { 1846 case 1847 "libtool", 1848 ar, 1849 filepath.Base(ar), 1850 cc: 1851 1852 var prev string 1853 for _, v := range it.Arguments { 1854 switch prev { 1855 case "-o", "-MT", "-MF": 1856 // nop 1857 default: 1858 if strings.HasSuffix(v, ".o") { 1859 r = append(r, filepath.Join(it.Directory, v)) 1860 } 1861 } 1862 prev = v 1863 } 1864 return r 1865 default: 1866 panic(todo("cc: %q ar: %q it: %+v", cc, ar, it)) 1867 } 1868 } 1869 1870 type cdbMakeWriter struct { 1871 ar string 1872 arBase string 1873 b bytes.Buffer 1874 cc string 1875 dir string 1876 err error 1877 it cdbItem 1878 parser func(s string) ([]string, error) 1879 prefix string 1880 sc *bufio.Scanner 1881 t *Task 1882 w *cdbWriter 1883 } 1884 1885 func (t *Task) newCdbMakeWriter(w *cdbWriter, dir string, parser func(s string) ([]string, error)) *cdbMakeWriter { 1886 const sz = 1 << 16 1887 r := &cdbMakeWriter{ 1888 ar: t.arLookPath, 1889 arBase: filepath.Base(t.arLookPath), 1890 cc: t.ccLookPath, 1891 dir: dir, 1892 parser: parser, 1893 t: t, 1894 w: w, 1895 } 1896 r.sc = bufio.NewScanner(&r.b) 1897 r.sc.Buffer(make([]byte, sz), sz) 1898 return r 1899 } 1900 1901 func (w *cdbMakeWriter) fail(err error) { 1902 if w.err == nil { 1903 w.err = fmt.Errorf("%v (%v)", err, origin(2)) 1904 } 1905 } 1906 1907 func (w *cdbMakeWriter) Write(b []byte) (int, error) { 1908 w.b.Write(b) 1909 for bytes.Contains(w.b.Bytes(), []byte{'\n'}) { 1910 if !w.sc.Scan() { 1911 panic(todo("internal error")) 1912 } 1913 1914 s := w.sc.Text() 1915 if strings.HasSuffix(s, "\\") { 1916 w.prefix += s[:len(s)-1] 1917 continue 1918 } 1919 1920 s = w.prefix + s 1921 w.prefix = "" 1922 s = strings.TrimSpace(s) 1923 if edx := strings.Index(s, "Entering directory"); edx >= 0 { 1924 s = s[edx+len("Entering directory"):] 1925 s = strings.TrimSpace(s) 1926 if len(s) == 0 { 1927 continue 1928 } 1929 1930 if (s[0] == '\'' || s[0] == '`') && s[len(s)-1] == '\'' { 1931 s = s[1:] 1932 if len(s) == 0 { 1933 continue 1934 } 1935 1936 s = s[:len(s)-1] 1937 } 1938 s = `"` + s + `"` 1939 dir, err := strconv.Unquote(s) 1940 if err != nil { 1941 w.fail(err) 1942 continue 1943 } 1944 1945 dir = filepath.Clean(dir) 1946 if dir == w.dir { 1947 continue 1948 } 1949 1950 w.dir = dir 1951 fmt.Printf("cd %s\n", dir) 1952 continue 1953 } 1954 1955 if dmesgs { 1956 dmesg("%v: source line `%s`", origin(1), s) 1957 } 1958 args, err := w.parser(s) 1959 if dmesgs { 1960 dmesg("%v: parser -> %v %[2]q, %v", origin(1), args, err) 1961 } 1962 if err != nil { 1963 w.fail(err) 1964 continue 1965 } 1966 1967 if len(args) == 0 { 1968 continue 1969 } 1970 1971 // TODO: change so eg handleGCC returns []cdbItem, skip if none. 1972 1973 w.it = cdbItem{} 1974 1975 err = nil 1976 switch args[0] { 1977 case w.cc: 1978 if w.t.verboseCompiledb { 1979 fmt.Printf("source line: %q\n", s) 1980 } 1981 fmt.Printf("CCGO CC: %q\n", args) 1982 err = w.handleGCC(args) 1983 case w.ar: 1984 fallthrough 1985 case w.arBase: 1986 if isCreateArchive(args[1]) { 1987 if w.t.verboseCompiledb { 1988 fmt.Printf("source line: %q\n", s) 1989 } 1990 fmt.Printf("CCGO AR: %q\n", args) 1991 err = w.handleAR(args) 1992 } 1993 case "libtool": 1994 if w.t.verboseCompiledb { 1995 fmt.Printf("source line: %q\n", s) 1996 } 1997 fmt.Printf("CCGO LIBTOOL: %q\n", args) 1998 err = w.handleLibtool(args) 1999 } 2000 if err != nil { 2001 w.fail(err) 2002 continue 2003 } 2004 2005 if w.it.Output != "" { 2006 w.w.add(w.it) 2007 } 2008 } 2009 return len(b), nil 2010 } 2011 2012 func (w *cdbMakeWriter) handleLibtool(args []string) error { 2013 w.it = cdbItem{ 2014 Arguments: args, 2015 Directory: w.dir, 2016 } 2017 for i, v := range args { 2018 switch { 2019 case v == "-o" && i < len(args)-1: 2020 w.it.Output = filepath.Join(w.dir, args[i+1]) 2021 } 2022 } 2023 w.it.output(w.cc, w.ar) 2024 return nil 2025 } 2026 2027 func (w *cdbMakeWriter) handleAR(args []string) error { 2028 w.it = cdbItem{ 2029 Arguments: args, 2030 Directory: w.dir, 2031 } 2032 // TODO: assumes isCreateArchive has already been checked 2033 w.it.Output = filepath.Join(w.dir, args[2]) 2034 return nil 2035 } 2036 2037 func (w *cdbMakeWriter) handleGCC(args []string) error { 2038 w.it = cdbItem{ 2039 Arguments: args, 2040 Directory: w.dir, 2041 } 2042 for i, v := range args { 2043 switch { 2044 case v == "-o" && i < len(args)-1: 2045 w.it.Output = filepath.Join(w.dir, args[i+1]) 2046 case strings.HasSuffix(v, ".c"): 2047 if w.it.File != "" { 2048 return fmt.Errorf("multiple .c files: %s", v) 2049 } 2050 2051 w.it.File = filepath.Clean(v) 2052 } 2053 } 2054 w.it.output(w.cc, w.ar) 2055 return nil 2056 } 2057 2058 type cdbWriter struct { 2059 w *bufio.Writer 2060 items []cdbItem 2061 } 2062 2063 func newCDBWriter(w io.Writer) *cdbWriter { 2064 return &cdbWriter{w: bufio.NewWriter(w)} 2065 } 2066 2067 func (w *cdbWriter) add(item cdbItem) { 2068 w.items = append(w.items, item) 2069 } 2070 2071 func (w *cdbWriter) finish() error { 2072 enc := json.NewEncoder(w.w) 2073 enc.SetIndent("", " ") 2074 if err := enc.Encode(w.items); err != nil { 2075 return err 2076 } 2077 return w.w.Flush() 2078 } 2079 2080 func join(sep string, a ...interface{}) string { 2081 var b []string 2082 for _, v := range a { 2083 switch x := v.(type) { 2084 case string: 2085 b = append(b, x) 2086 case []string: 2087 b = append(b, x...) 2088 default: 2089 panic(todo("internal error: %T", x)) 2090 } 2091 } 2092 return strings.Join(b, sep) 2093 } 2094 2095 func inDir(dir string, f func() error) (err error) { 2096 var cwd string 2097 if cwd, err = os.Getwd(); err != nil { 2098 return err 2099 } 2100 2101 defer func() { 2102 if err2 := os.Chdir(cwd); err2 != nil { 2103 err = err2 2104 } 2105 }() 2106 2107 if err = os.Chdir(dir); err != nil { 2108 return err 2109 } 2110 2111 return f() 2112 } 2113 2114 func detectMingw(s string) bool { 2115 return strings.Contains(s, "#define __MINGW") 2116 } 2117 2118 func memGuard(i int, force bool) { 2119 if totalRam == 0 || totalRam > 64e9 { 2120 return 2121 } 2122 2123 var ms runtime.MemStats 2124 runtime.ReadMemStats(&ms) 2125 switch { 2126 case ms.Alloc < totalRam/2: 2127 return 2128 case ms.Alloc < (8*totalRam)/10: 2129 if force { 2130 break 2131 } 2132 2133 switch { 2134 case totalRam < 1e9: 2135 // ok 2136 case totalRam < 16e9: 2137 if i&1 == 1 { 2138 return 2139 } 2140 default: 2141 if i&3 != 3 { 2142 return 2143 } 2144 } 2145 } 2146 2147 debug.FreeOSMemory() 2148 }