summaryrefslogtreecommitdiffstats
path: root/vendor/modernc.org/ccgo/v3/lib/ccgo.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/modernc.org/ccgo/v3/lib/ccgo.go')
-rw-r--r--vendor/modernc.org/ccgo/v3/lib/ccgo.go2063
1 files changed, 2063 insertions, 0 deletions
diff --git a/vendor/modernc.org/ccgo/v3/lib/ccgo.go b/vendor/modernc.org/ccgo/v3/lib/ccgo.go
new file mode 100644
index 00000000..a5ed804f
--- /dev/null
+++ b/vendor/modernc.org/ccgo/v3/lib/ccgo.go
@@ -0,0 +1,2063 @@
+// Copyright 2020 The CCGO Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:generate stringer -output stringer.go -type=exprMode,opKind
+
+// Package ccgo implements the ccgo command.
+package ccgo // import "modernc.org/ccgo/v3/lib"
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/csv"
+ "encoding/json"
+ "fmt"
+ "go/ast"
+ "go/build"
+ "go/parser"
+ "go/token"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "runtime/debug"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/kballard/go-shellquote"
+ "golang.org/x/tools/go/packages"
+ "modernc.org/cc/v3"
+ "modernc.org/libc"
+ "modernc.org/opt"
+)
+
+const (
+ Version = "3.12.6-20210922111124"
+
+ experimentsEnvVar = "CCGO_EXPERIMENT"
+ maxSourceLine = 1 << 20
+)
+
+var (
+ _ = libc.Xstdin
+
+ coverExperiment bool
+)
+
+func init() {
+ s := strings.TrimSpace(os.Getenv(experimentsEnvVar))
+ if s == "" {
+ return
+ }
+
+ for _, v := range strings.Split(s, ",") {
+ switch strings.TrimSpace(v) {
+ case "cover":
+ coverExperiment = true
+ }
+ }
+}
+
+//TODO CPython
+//TODO Cython
+//TODO gmp
+//TODO gofrontend
+//TODO gsl
+//TODO minigmp
+//TODO mpc
+//TODO mpfr
+//TODO pcre
+//TODO pcre2
+//TODO quickjs
+//TODO redis
+//TODO sdl2
+//TODO wolfssl
+//TODO zdat
+//TODO zstd
+
+//TODO 2020-07-17
+//
+// Fix += and friends
+//
+// Audit all unsafe.Pointer conversions
+//
+// Remove double dereferencing **
+//
+// Shifts must not use n.Promote on left opearand
+//
+// Un-array
+//
+// Pass more CSmith tests.
+
+//TODO merge VaList slots of distinct top level statements.
+
+//TODO turn void
+//
+// a = b = c = d
+//
+// where all but the first and last of a, b, c, ... are declarators, into
+//
+// c = d
+// b = c
+// a = b
+
+//TODO define and use all tagged struct types, including inner ones, for example SQLite's SrcList_item.
+
+//TODO rewrite return conditionalExpression so it has no closures. Partially done.
+
+//TODO define and restore simple named constants. Having
+//
+// #define FOO 42
+// ...
+// case FOO:
+//
+// we do not yet define FOO and generate
+//
+// case 42:
+
+//TODO do not generate a terminating semicolon for empty statements.
+
+//TODO replace
+//
+// var sqlite3_data_directory uintptr = uintptr(0) /* sqlite3.c:156345:17 */
+//
+// by
+//
+// var sqlite3_data_directory uintptr = 0 /* sqlite3.c:156345:17 */
+//
+// or
+//
+// var sqlite3_data_directory = uintptr(0) /* sqlite3.c:156345:17 */
+
+//TODO drop all non-referenced declarators unless forced by a command line flag.
+
+const (
+ builtin = `
+#ifdef __PTRDIFF_TYPE__
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+#else
+#error __PTRDIFF_TYPE__ undefined
+#endif
+
+#ifdef __SIZE_TYPE__
+typedef __SIZE_TYPE__ size_t;
+#else
+#error __SIZE_TYPE__ undefined
+#endif
+
+#ifdef __WCHAR_TYPE__
+typedef __WCHAR_TYPE__ wchar_t;
+#else
+#error __WCHAR_TYPE__ undefined
+#endif
+
+#ifdef __SIZEOF_INT128__
+typedef struct { __INT64_TYPE__ lo, hi; } __int128_t; // must match modernc.org/mathutil.Int128
+typedef struct { __UINT64_TYPE__ lo, hi; } __uint128_t; // must match modernc.org/mathutil.Int128
+#endif;
+
+#define _FILE_OFFSET_BITS 64
+#define __FUNCTION__ __func__
+#define __PRETTY_FUNCTION__ __func__
+#define __asm __asm__
+#define __builtin_constant_p(x) __builtin_constant_p_impl(0, x)
+#define __builtin_offsetof(type, member) ((__SIZE_TYPE__)&(((type*)0)->member))
+#define __builtin_va_arg(ap, type) ((type)__ccgo_va_arg(ap))
+#define __builtin_va_copy(dst, src) dst = src
+#define __builtin_va_end(ap) __ccgo_va_end(ap)
+#define __builtin_va_start(ap, v) __ccgo_va_start(ap)
+#define __ccgo_fd_zero(set) __builtin_memset(set, 0, sizeof(fd_set))
+#define __ccgo_tcl_default_double_rounding(set) ((void)0)
+#define __ccgo_tcl_ieee_double_rounding(set) ((void)0)
+#define __extension__
+#define __has_include(...) __has_include_impl(#__VA_ARGS__)
+#define __has_include_impl(x)
+#define __inline__ inline
+#define __signed signed
+#define asm __asm__
+#define in6addr_any (*__ccgo_in6addr_anyp())
+
+typedef void *__builtin_va_list;
+typedef long double __float128;
+
+#if defined(__MINGW32__) || defined(__MINGW64__)
+typedef __builtin_va_list va_list;
+int gnu_printf(const char *format, ...);
+int gnu_scanf(const char *format, ...);
+int ms_printf(const char *format, ...);
+int ms_scanf(const char *format, ...);
+#define _VA_LIST_DEFINED
+#define __extension__
+#endif
+
+__UINT16_TYPE__ __builtin_bswap16 (__UINT16_TYPE__ x);
+__UINT32_TYPE__ __builtin_bswap32 (__UINT32_TYPE__ x);
+__UINT64_TYPE__ __builtin_bswap64 (__UINT64_TYPE__ x);
+char *__builtin___strcat_chk (char *dest, const char *src, size_t os);
+char *__builtin___strcpy_chk (char *dest, const char *src, size_t os);
+char *__builtin___strncpy_chk(char *dest, char *src, size_t n, size_t os);
+char *__builtin_strchr(const char *s, int c);
+char *__builtin_strcpy(char *dest, const char *src);
+double __builtin_copysign ( double x, double y );
+double __builtin_copysignl (long double x, long double y );
+double __builtin_fabs(double x);
+double __builtin_huge_val (void);
+double __builtin_inf (void);
+double __builtin_nan (const char *str);
+float __builtin_copysignf ( float x, float y );
+float __builtin_fabsf(float x);
+float __builtin_huge_valf (void);
+float __builtin_inff (void);
+float __builtin_nanf (const char *str);
+int __builtin___snprintf_chk (char *s, size_t maxlen, int flag, size_t os, const char *fmt, ...);
+int __builtin___sprintf_chk (char *s, int flag, size_t os, const char *fmt, ...);
+int __builtin___vsnprintf_chk (char *s, size_t maxlen, int flag, size_t os, const char *fmt, __builtin_va_list ap);
+int __builtin__snprintf_chk(char * str, size_t maxlen, int flag, size_t strlen, const char * format);
+int __builtin_abs(int j);
+int __builtin_add_overflow();
+int __builtin_clz (unsigned);
+int __builtin_isunordered(double x, double y);
+int __builtin_clzl (unsigned long);
+int __builtin_clzll (unsigned long long);
+int __builtin_constant_p_impl(int, ...);
+int __builtin_getentropy(void*, size_t);
+int __builtin_isnan(double);
+int __builtin_memcmp(const void *s1, const void *s2, size_t n);
+int __builtin_mul_overflow();
+int __builtin_popcount (unsigned int x);
+int __builtin_popcountl (unsigned long x);
+int __builtin_printf(const char *format, ...);
+int __builtin_snprintf(char *str, size_t size, const char *format, ...);
+int __builtin_sprintf(char *str, const char *format, ...);
+int __builtin_strcmp(const char *s1, const char *s2);
+int __builtin_sub_overflow();
+long __builtin_expect (long exp, long c);
+long double __builtin_fabsl(long double x);
+long double __builtin_nanl (const char *str);
+long long __builtin_llabs(long long j);
+size_t __builtin_object_size (void * ptr, int type);
+size_t __builtin_strlen(const char *s);
+void *__builtin___memcpy_chk (void *dest, const void *src, size_t n, size_t os);
+void *__builtin___memmove_chk (void *dest, const void *src, size_t n, size_t os);
+void *__builtin___memset_chk (void *dstpp, int c, size_t len, size_t dstlen);
+void *__builtin_malloc(size_t size);
+void *__builtin_memcpy(void *dest, const void *src, size_t n);
+void *__builtin_memset(void *s, int c, size_t n);
+void *__builtin_mmap(void *addr, size_t length, int prot, int flags, int fd, __INTPTR_TYPE__ offset);
+void *__ccgo_va_arg(__builtin_va_list ap);
+void __builtin_abort(void);
+void __builtin_bzero(void *s, size_t n);
+void __builtin_exit(int status);
+void __builtin_free(void *ptr);
+void __builtin_prefetch (const void *addr, ...);
+void __builtin_trap (void);
+void __builtin_unreachable (void);
+void __ccgo_dmesg(char*, ...);
+void __ccgo_va_end(__builtin_va_list ap);
+void __ccgo_va_start(__builtin_va_list ap);
+
+#define __sync_add_and_fetch(ptr, val) \
+ __builtin_choose_expr( \
+ __builtin_types_compatible_p(typeof(*ptr), unsigned), \
+ __sync_add_and_fetch_uint32(ptr, val), \
+ __TODO__ \
+ )
+
+#define __sync_fetch_and_add(ptr, val) \
+ __TODO__ \
+
+#define __sync_sub_and_fetch(ptr, val) \
+ __builtin_choose_expr( \
+ __builtin_types_compatible_p(typeof(*ptr), unsigned), \
+ __sync_sub_and_fetch_uint32(ptr, val), \
+ __TODO__ \
+ )
+
+unsigned __sync_add_and_fetch_uint32(unsigned*, unsigned);
+unsigned __sync_sub_and_fetch_uint32(unsigned*, unsigned);
+
+#ifdef __APPLE__
+int (*__darwin_check_fd_set_overflow)(int, void *, int);
+#endif
+
+`
+ defaultCrt = "modernc.org/libc"
+)
+
+func origin(skip int) string {
+ pc, fn, fl, _ := runtime.Caller(skip)
+ f := runtime.FuncForPC(pc)
+ var fns string
+ if f != nil {
+ fns = f.Name()
+ if x := strings.LastIndex(fns, "."); x > 0 {
+ fns = fns[x+1:]
+ }
+ }
+ return fmt.Sprintf("%s:%d:%s", fn, fl, fns)
+}
+
+func todo(s string, args ...interface{}) string {
+ switch {
+ case s == "":
+ s = fmt.Sprintf(strings.Repeat("%v ", len(args)), args...)
+ default:
+ s = fmt.Sprintf(s, args...)
+ }
+ r := fmt.Sprintf("%s\n\tTODO %s", origin(2), s) //TODOOK
+ fmt.Fprintf(os.Stdout, "%s\n", r)
+ os.Stdout.Sync()
+ return r
+}
+
+func trc(s string, args ...interface{}) string {
+ switch {
+ case s == "":
+ s = fmt.Sprintf(strings.Repeat("%v ", len(args)), args...)
+ default:
+ s = fmt.Sprintf(s, args...)
+ }
+ r := fmt.Sprintf("%s: TRC %s", origin(2), s)
+ fmt.Fprintf(os.Stderr, "%s\n", r)
+ os.Stderr.Sync()
+ return r
+}
+
+// Task represents a compilation job.
+type Task struct {
+ D []string // -D
+ I []string // -I
+ U []string // -U
+ ar string // $AR, default "ar"
+ arLookPath string // LookPath(ar)
+ args []string
+ asts []*cc.AST
+ capif string
+ saveConfig string // -save-config
+ saveConfigErr error
+ cc string // $CC, default "gcc"
+ ccLookPath string // LookPath(cc)
+ cdb string // foo.json, use compile DB
+ cfg *cc.Config
+ compiledb string // -compiledb
+ crt string
+ crtImportPath string // -crt-import-path
+ exportDefines string // -export-defines
+ exportEnums string // -export-enums
+ exportExterns string // -export-externs
+ exportFields string // -export-fields
+ exportStructs string // -export-structs
+ exportTypedefs string // -export-typedefs
+ goarch string
+ goos string
+ hide map[string]struct{} // -hide
+ hostConfigCmd string // -host-config-cmd
+ hostConfigOpts string // -host-config-opts
+ hostIncludes []string
+ hostPredefined string
+ hostSysIncludes []string
+ ignoredIncludes string // -ignored-includes
+ ignoredObjects map[string]struct{} // -ignore-object
+ imported []*imported
+ includedFiles map[string]struct{}
+ l []string // -l
+ loadConfig string // --load-config
+ o string // -o
+ out io.Writer
+ pkgName string // -pkgname
+ replaceFdZero string // -replace-fd-zero
+ replaceTclDefaultDoubleRounding string // -replace-tcl-default-double-rounding
+ replaceTclIeeeDoubleRounding string // -replace-tcl-default-double-rounding
+ scriptFn string // -script
+ sources []cc.Source
+ staticLocalsPrefix string // -static-locals-prefix
+ stderr io.Writer
+ stdout io.Writer
+ symSearchOrder []int // >= 0: asts[i], < 0 : imported[-i-1]
+ verboseCompiledb bool // -verbose-compiledb
+ volatiles map[cc.StringID]struct{} // -volatile
+
+ // Path to a binary that will be called instead of executing
+ // Task.Main(). Intended to support TestGenerate in stable vs latest
+ // modes. This is _not_ supposed to be used when the Task instance is
+ // constructed by a ccgo _command_ (ccgo/v3) - it should never set this
+ // field. Only programs importing ccgo/v3/lib that opt-in into this
+ // feature should ever set it.
+ CallOutBinary string
+
+ E bool // -E
+ allErrors bool // -all-errors
+ compiledbValid bool // -compiledb present
+ configSaved bool
+ configured bool // hostPredefined, hostIncludes, hostSysIncludes are valid
+ cover bool // -cover-instrumentation
+ coverC bool // -cover-instrumentation-c
+ defaultUnExport bool // -unexported-by-default
+ errTrace bool // -err-trace
+ exportDefinesValid bool // -export-defines present
+ exportEnumsValid bool // -export-enums present
+ exportExternsValid bool // -export-externs present
+ exportFieldsValid bool // -export-fields present
+ exportStructsValid bool // -export-structs present
+ exportTypedefsValid bool // -export-typedefs present
+ fullPathComments bool // -full-path-comments
+ funcSig bool // -func-sig
+ header bool // -header
+ ignoreUnsupportedAligment bool // -ignore-unsupported-alignment
+ isScripted bool
+ mingw bool
+ noCapi bool // -nocapi
+ nostdinc bool // -nostdinc
+ nostdlib bool // -nostdlib
+ panicStubs bool // -panic-stubs
+ tracePinning bool // -trace-pinning
+ traceTranslationUnits bool // -trace-translation-units
+ verifyStructs bool // -verify-structs
+ version bool // -version
+ watch bool // -watch-instrumentation
+ windows bool // -windows
+}
+
+// NewTask returns a newly created Task.
+func NewTask(args []string, stdout, stderr io.Writer) *Task {
+ if dmesgs {
+ dmesg("%v: %v", origin(1), args)
+ }
+ if stdout == nil {
+ stdout = os.Stdout
+ }
+ if stderr == nil {
+ stderr = os.Stderr
+ }
+ return &Task{
+ args: args,
+ cfg: &cc.Config{
+ Config3: cc.Config3{
+ MaxSourceLine: maxSourceLine,
+ },
+ DoNotTypecheckAsm: true,
+ EnableAssignmentCompatibilityChecking: true,
+ LongDoubleIsDouble: true,
+ SharedFunctionDefinitions: &cc.SharedFunctionDefinitions{},
+ },
+ ar: env("AR", "ar"),
+ cc: env("CC", "gcc"),
+ crt: "libc.",
+ crtImportPath: defaultCrt,
+ goarch: env("TARGET_GOARCH", env("GOARCH", runtime.GOARCH)),
+ goos: env("TARGET_GOOS", env("GOOS", runtime.GOOS)),
+ hide: map[string]struct{}{},
+ hostConfigCmd: env("CCGO_CPP", ""),
+ pkgName: "main",
+ stderr: stderr,
+ stdout: stdout,
+ volatiles: map[cc.StringID]struct{}{},
+ }
+}
+
+func env(name, deflt string) (r string) {
+ r = deflt
+ if s := os.Getenv(name); s != "" {
+ r = s
+ }
+ return r
+}
+
+// Get exported symbols from package having import path 'path'.
+func (t *Task) capi(path string) (pkgName string, exports map[string]struct{}, err error) {
+ // defer func() {
+ // var a []string
+ // for k := range exports {
+ // a = append(a, k)
+ // }
+ // sort.Strings(a)
+ // trc("%s\n%s", path, strings.Join(a, "\n"))
+ // }()
+ var errModule, errGopath error
+ defer func() {
+ if err != nil {
+ a := []string{err.Error()}
+ if errModule != nil {
+ a = append(a, fmt.Sprintf("module mode error: %s", errModule))
+ }
+ if errGopath != nil {
+ a = append(a, fmt.Sprintf("gopath mode error: %s", errGopath))
+ }
+ wd, err2 := os.Getwd()
+ err = fmt.Errorf(
+ "(wd %q, %v): loading C exports from %s (GOPATH=%v GO111MODULE=%v): %v",
+ wd, err2, path, os.Getenv("GOPATH"), os.Getenv("GO111MODULE"), strings.Join(a, "\n\t"),
+ )
+ }
+ }()
+
+ mod := os.Getenv("GO111MODULE")
+ if mod == "" || mod == "on" {
+ var pkgs []*packages.Package
+ pkgs, errModule = packages.Load(
+ &packages.Config{
+ Mode: packages.NeedFiles,
+ Env: append(os.Environ(), fmt.Sprintf("GOOS=%s", t.goos), fmt.Sprintf("GOARCH=%s", t.goarch)),
+ },
+ path,
+ )
+ switch {
+ case errModule == nil:
+ if len(pkgs) != 1 {
+ errModule = fmt.Errorf("expected one package, loaded %d", len(pkgs))
+ break
+ }
+
+ pkg := pkgs[0]
+ if len(pkg.Errors) != 0 {
+ var a []string
+ for _, v := range pkg.Errors {
+ a = append(a, v.Error())
+ }
+ errModule = fmt.Errorf("%s", strings.Join(a, "\n"))
+ break
+ }
+
+ return t.capi2(pkg.GoFiles)
+ }
+ }
+
+ gopath0 := os.Getenv("GOPATH")
+ for _, gopath := range strings.Split(gopath0, string(os.PathListSeparator)) {
+ if gopath == "" || !filepath.IsAbs(gopath) {
+ continue
+ }
+
+ ctx := build.Context{
+ GOARCH: t.goarch,
+ GOOS: t.goos,
+ GOPATH: gopath,
+ Compiler: "gc",
+ }
+ arg := filepath.Join(gopath, "src", path)
+ pkg, err := ctx.ImportDir(arg, 0)
+ if err != nil {
+ errGopath = err
+ continue
+ }
+
+ for i, v := range pkg.GoFiles {
+ pkg.GoFiles[i] = filepath.Join(gopath, "src", path, v)
+ }
+ return t.capi2(pkg.GoFiles)
+ }
+ return "", nil, fmt.Errorf("cannot load CAPI")
+}
+
+func (t *Task) capi2(files []string) (pkgName string, exports map[string]struct{}, err error) {
+ exports = map[string]struct{}{}
+ base := fmt.Sprintf("capi_%s_%s.go", t.goos, t.goarch)
+ var fn string
+ for _, v := range files {
+ if filepath.Base(v) == base {
+ fn = v
+ break
+ }
+ }
+ if fn == "" {
+ return "", nil, fmt.Errorf("file %s not found", base)
+ }
+
+ fset := token.NewFileSet()
+ file, err := parser.ParseFile(fset, fn, nil, 0)
+ if err != nil {
+ return "", nil, err
+ }
+
+ obj, ok := file.Scope.Objects["CAPI"]
+ if !ok {
+ return "", nil, fmt.Errorf("CAPI not declared in %s", fn)
+ }
+
+ switch obj.Kind {
+ case ast.Var:
+ // ok
+ default:
+ return "", nil, fmt.Errorf("unexpected CAPI object kind: %v", obj.Kind)
+ }
+
+ spec, ok := obj.Decl.(*ast.ValueSpec)
+ if !ok {
+ return "", nil, fmt.Errorf("unexpected CAPI object type: %T", obj.Decl)
+ }
+
+ if len(spec.Values) != 1 {
+ return "", nil, fmt.Errorf("expected one CAPI expression, got %v", len(spec.Values))
+ }
+
+ ast.Inspect(spec.Values[0], func(n ast.Node) bool {
+ if x, ok := n.(*ast.BasicLit); ok {
+ var key string
+ if key, err = strconv.Unquote(x.Value); err != nil {
+ err = fmt.Errorf("invalid CAPI key value: %s", x.Value)
+ return false
+ }
+
+ exports[key] = struct{}{}
+ }
+ return true
+ })
+ return file.Name.String(), exports, err
+}
+
+// Main executes task.
+func (t *Task) Main() (err error) {
+ if dmesgs {
+ defer func() {
+ if err != nil {
+ // trc("FAIL %p: %q: %v", t, t.args, err)
+ dmesg("%v: returning from Task.Main: %v", origin(1), err)
+ }
+ }()
+
+ }
+
+ defer func() {
+ if t.saveConfigErr != nil && err == nil {
+ err = t.saveConfigErr
+ }
+ }()
+
+ if !t.isScripted && coverExperiment {
+ defer func() {
+ fmt.Fprintf(os.Stderr, "cover report:\n%s\n", coverReport())
+ }()
+ }
+ if t.CallOutBinary != "" {
+ if dmesgs {
+ dmesg("%v: calling out '%s' instead", origin(1))
+ }
+ cmd := exec.Command(t.CallOutBinary, t.args[1:]...)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ err = fmt.Errorf("%v\n%s", err, out)
+ }
+ return err
+ }
+
+ opts := opt.NewSet()
+ opts.Arg("D", true, func(arg, value string) error { t.D = append(t.D, value); return nil })
+ opts.Arg("I", true, func(opt, arg string) error { t.I = append(t.I, arg); return nil })
+ opts.Arg("U", true, func(arg, value string) error { t.U = append(t.U, value); return nil })
+ opts.Arg("compiledb", false, func(arg, value string) error { t.compiledb = value; t.compiledbValid = true; return opt.Skip(nil) })
+ opts.Arg("crt-import-path", false, func(arg, value string) error { t.crtImportPath = value; return nil })
+ opts.Arg("export-defines", false, func(arg, value string) error { t.exportDefines = value; t.exportDefinesValid = true; return nil })
+ opts.Arg("export-enums", false, func(arg, value string) error { t.exportEnums = value; t.exportEnumsValid = true; return nil })
+ opts.Arg("export-externs", false, func(arg, value string) error { t.exportExterns = value; t.exportExternsValid = true; return nil })
+ opts.Arg("export-fields", false, func(arg, value string) error { t.exportFields = value; t.exportFieldsValid = true; return nil })
+ opts.Arg("export-structs", false, func(arg, value string) error { t.exportStructs = value; t.exportStructsValid = true; return nil })
+ opts.Arg("export-typedefs", false, func(arg, value string) error { t.exportTypedefs = value; t.exportTypedefsValid = true; return nil })
+ opts.Arg("host-config-cmd", false, func(arg, value string) error { t.hostConfigCmd = value; return nil })
+ opts.Arg("host-config-opts", false, func(arg, value string) error { t.hostConfigOpts = value; return nil })
+ opts.Arg("ignored-includes", false, func(arg, value string) error { t.ignoredIncludes = value; return nil })
+ opts.Arg("pkgname", false, func(arg, value string) error { t.pkgName = value; return nil })
+ opts.Arg("replace-fd-zero", false, func(arg, value string) error { t.replaceFdZero = value; return nil })
+ opts.Arg("replace-tcl-default-double-rounding", false, func(arg, value string) error { t.replaceTclDefaultDoubleRounding = value; return nil })
+ opts.Arg("replace-tcl-ieee-double-rounding", false, func(arg, value string) error { t.replaceTclIeeeDoubleRounding = value; return nil })
+ opts.Arg("script", false, func(arg, value string) error { t.scriptFn = value; return nil })
+ opts.Arg("static-locals-prefix", false, func(arg, value string) error { t.staticLocalsPrefix = value; return nil })
+
+ opts.Opt("E", func(opt string) error { t.E = true; return nil })
+ opts.Opt("all-errors", func(opt string) error { t.allErrors = true; return nil })
+ opts.Opt("cover-instrumentation", func(opt string) error { t.cover = true; return nil })
+ opts.Opt("cover-instrumentation-c", func(opt string) error { t.coverC = true; return nil })
+ opts.Opt("err-trace", func(opt string) error { t.errTrace = true; return nil })
+ opts.Opt("full-path-comments", func(opt string) error { t.fullPathComments = true; return nil })
+ opts.Opt("func-sig", func(opt string) error { t.funcSig = true; return nil })
+ opts.Opt("header", func(opt string) error { t.header = true; return nil })
+ opts.Opt("ignore-unsupported-alignment", func(opt string) error { t.ignoreUnsupportedAligment = true; return nil })
+ opts.Opt("nocapi", func(opt string) error { t.noCapi = true; return nil })
+ opts.Opt("nostdinc", func(opt string) error { t.nostdinc = true; return nil })
+ opts.Opt("panic-stubs", func(opt string) error { t.panicStubs = true; return nil })
+ opts.Opt("trace-pinning", func(opt string) error { t.tracePinning = true; return nil })
+ opts.Opt("trace-translation-units", func(opt string) error { t.traceTranslationUnits = true; return nil })
+ opts.Opt("unexported-by-default", func(opt string) error { t.defaultUnExport = true; return nil })
+ opts.Opt("verbose-compiledb", func(opt string) error { t.verboseCompiledb = true; return nil })
+ opts.Opt("verify-structs", func(opt string) error { t.verifyStructs = true; return nil })
+ opts.Opt("version", func(opt string) error { t.version = true; return nil })
+ opts.Opt("watch-instrumentation", func(opt string) error { t.watch = true; return nil })
+ opts.Opt("windows", func(opt string) error { t.windows = true; return nil })
+
+ opts.Opt("trace-included-files", func(opt string) error {
+ if t.includedFiles == nil {
+ t.includedFiles = map[string]struct{}{}
+ }
+ prev := t.cfg.IncludeFileHandler
+ t.cfg.IncludeFileHandler = func(pos token.Position, pathName string) {
+ if prev != nil {
+ prev(pos, pathName)
+ }
+ if _, ok := t.includedFiles[pathName]; !ok {
+ t.includedFiles[pathName] = struct{}{}
+ fmt.Fprintf(os.Stderr, "#include %s\n", pathName)
+ }
+ }
+ return nil
+ })
+ opts.Arg("ignore-object", false, func(arg, value string) error {
+ if t.ignoredObjects == nil {
+ t.ignoredObjects = map[string]struct{}{}
+ }
+ t.ignoredObjects[value] = struct{}{}
+ return nil
+ })
+ opts.Arg("save-config", false, func(arg, value string) error {
+ if value == "" {
+ return nil
+ }
+
+ abs, err := filepath.Abs(value)
+ if err != nil {
+ return err
+ }
+
+ t.saveConfig = abs
+ if t.includedFiles == nil {
+ t.includedFiles = map[string]struct{}{}
+ }
+ prev := t.cfg.IncludeFileHandler
+ t.cfg.IncludeFileHandler = func(pos token.Position, pathName string) {
+ if prev != nil {
+ prev(pos, pathName)
+ }
+ if _, ok := t.includedFiles[pathName]; !ok {
+ t.includedFiles[pathName] = struct{}{}
+ full := filepath.Join(abs, pathName)
+ switch _, err := os.Stat(full); {
+ case err != nil && os.IsNotExist(err):
+ // ok
+ case err != nil:
+ t.saveConfigErr = err
+ return
+ default:
+ return
+ }
+
+ b, err := ioutil.ReadFile(pathName)
+ if err != nil {
+ t.saveConfigErr = err
+ return
+ }
+
+ dir, _ := filepath.Split(full)
+ if err := os.MkdirAll(dir, 0700); err != nil {
+ t.saveConfigErr = err
+ return
+ }
+
+ if err := ioutil.WriteFile(full, b, 0600); err != nil {
+ t.saveConfigErr = err
+ }
+ }
+ }
+ return nil
+ })
+ opts.Arg("-load-config", false, func(arg, value string) error {
+ if value == "" {
+ return nil
+ }
+
+ abs, err := filepath.Abs(value)
+ if err != nil {
+ return err
+ }
+
+ t.loadConfig = abs
+ return nil
+ })
+ opts.Arg("volatile", false, func(arg, value string) error {
+ for _, v := range strings.Split(strings.TrimSpace(value), ",") {
+ t.volatiles[cc.String(v)] = struct{}{}
+ }
+ return nil
+ })
+ opts.Opt("nostdlib", func(opt string) error {
+ t.nostdlib = true
+ t.crt = ""
+ t.crtImportPath = ""
+ return nil
+ })
+ opts.Arg("hide", false, func(arg, value string) error {
+ value = strings.TrimSpace(value)
+ a := strings.Split(value, ",")
+ for _, v := range a {
+ t.hide[v] = struct{}{}
+ }
+ return nil
+ })
+ opts.Arg("l", true, func(arg, value string) error {
+ value = strings.TrimSpace(value)
+ a := strings.Split(value, ",")
+ for _, v := range a {
+ t.l = append(t.l, v)
+ t.symSearchOrder = append(t.symSearchOrder, -len(t.l))
+ }
+ return nil
+ })
+ opts.Arg("o", false, func(arg, value string) error {
+ if t.o != "" {
+ return fmt.Errorf("multiple argument: -o %s", value)
+ }
+
+ t.o = value
+ return nil
+ })
+ if err := opts.Parse(t.args[1:], func(arg string) error {
+ if strings.HasPrefix(arg, "-") {
+ return fmt.Errorf("unexpected option: %s", arg)
+ }
+
+ switch filepath.Ext(arg) {
+ case ".h":
+ t.symSearchOrder = append(t.symSearchOrder, len(t.sources))
+ t.sources = append(t.sources, cc.Source{Name: arg})
+ case ".c":
+ t.symSearchOrder = append(t.symSearchOrder, len(t.sources))
+ t.sources = append(t.sources, cc.Source{Name: arg, DoNotCache: true})
+ case ".json":
+ t.cdb = arg
+ return opt.Skip(nil)
+ default:
+ return fmt.Errorf("unexpected file type: %s", arg)
+ }
+
+ return nil
+ }); err != nil {
+ switch x := err.(type) {
+ case opt.Skip:
+ switch {
+ case t.compiledbValid: // -compiledb foo.json, create DB
+ cmd := []string(x)[1:]
+ if len(cmd) == 0 {
+ return fmt.Errorf("missing command after -compiledb <file>")
+ }
+
+ return t.createCompileDB(cmd)
+ case t.cdb != "": // foo.json ..., use DB
+ if err := t.configure(); err != nil {
+ return err
+ }
+
+ return t.useCompileDB(t.cdb, x)
+ }
+
+ return err
+ default:
+ return err
+ }
+ }
+
+ if t.version {
+ gobin, err := exec.LookPath("go")
+ var b []byte
+ if err == nil {
+ var bin string
+ bin, err = exec.LookPath(os.Args[0])
+ if err == nil {
+ b, err = exec.Command(gobin, "version", "-m", bin).CombinedOutput()
+ }
+ }
+ if err == nil {
+ fmt.Fprintf(t.stdout, "%s", b)
+ return nil
+ }
+
+ fmt.Fprintf(t.stdout, "%s\n", Version)
+ return nil
+ }
+
+ if t.scriptFn != "" {
+ return t.scriptBuild(t.scriptFn)
+ }
+
+ if len(t.sources) == 0 {
+ return fmt.Errorf("no input files specified")
+ }
+
+ if t.crtImportPath != "" {
+ t.l = append(t.l, t.crtImportPath)
+ t.symSearchOrder = append(t.symSearchOrder, -len(t.l))
+ m := map[string]struct{}{}
+ for _, v := range t.l {
+ v = strings.TrimSpace(v)
+ if _, ok := m[v]; !ok {
+ t.imported = append(t.imported, &imported{path: v})
+ m[v] = struct{}{}
+ }
+ }
+ t.imported[len(t.imported)-1].used = true // crt is always imported
+ }
+
+ if err := t.configure(); err != nil {
+ return err
+ }
+
+ abi, err := cc.NewABI(t.goos, t.goarch)
+ abi.Types[cc.LongDouble] = abi.Types[cc.Double]
+ if err != nil {
+ return err
+ }
+
+ var re *regexp.Regexp
+ if t.ignoredIncludes != "" {
+ if re, err = regexp.Compile(t.ignoredIncludes); err != nil {
+ return err
+ }
+ }
+
+ t.cfg.ABI = abi
+ t.cfg.ReplaceMacroFdZero = t.replaceFdZero
+ t.cfg.ReplaceMacroTclDefaultDoubleRounding = t.replaceTclDefaultDoubleRounding
+ t.cfg.ReplaceMacroTclIeeeDoubleRounding = t.replaceTclIeeeDoubleRounding
+ t.cfg.Config3.IgnoreInclude = re
+ t.cfg.Config3.NoFieldAndBitfieldOverlap = true
+ t.cfg.Config3.PreserveWhiteSpace = t.saveConfig == ""
+ t.cfg.Config3.UnsignedEnums = true
+
+ if t.mingw = detectMingw(t.hostPredefined); t.mingw {
+ t.windows = true
+ }
+ if t.nostdinc {
+ t.hostIncludes = nil
+ t.hostSysIncludes = nil
+ }
+ var sources []cc.Source
+ if t.hostPredefined != "" {
+ sources = append(sources, cc.Source{Name: "<predefined>", Value: t.hostPredefined})
+ }
+ sources = append(sources, cc.Source{Name: "<builtin>", Value: builtin})
+ if len(t.D) != 0 {
+ var a []string
+ for _, v := range t.D {
+ if i := strings.IndexByte(v, '='); i > 0 {
+ a = append(a, fmt.Sprintf("#define %s %s", v[:i], v[i+1:]))
+ continue
+ }
+
+ a = append(a, fmt.Sprintf("#define %s 1", v))
+ }
+ a = append(a, "\n")
+ sources = append(sources, cc.Source{Name: "<defines>", Value: strings.Join(a, "\n"), DoNotCache: true})
+ }
+ if len(t.U) != 0 {
+ var a []string
+ for _, v := range t.U {
+ a = append(a, fmt.Sprintf("#undef %s", v))
+ }
+ a = append(a, "\n")
+ sources = append(sources, cc.Source{Name: "<undefines>", Value: strings.Join(a, "\n"), DoNotCache: true})
+ }
+
+ // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/c99.html
+ //
+ // Headers whose names are enclosed in double-quotes ( "" ) shall be
+ // searched for first in the directory of the file with the #include
+ // line, then in directories named in -I options, and last in the usual
+ // places
+ includePaths := append([]string{"@"}, t.I...)
+ includePaths = append(includePaths, t.hostIncludes...)
+ includePaths = append(includePaths, t.hostSysIncludes...)
+ // For headers whose names are enclosed in angle brackets ( "<>" ), the
+ // header shall be searched for only in directories named in -I options
+ // and then in the usual places.
+ sysIncludePaths := append(t.I, t.hostSysIncludes...)
+ if t.traceTranslationUnits {
+ fmt.Printf("target: %s/%s\n", t.goos, t.goarch)
+ if t.hostConfigCmd != "" {
+ fmt.Printf("host config cmd: %s\n", t.hostConfigCmd)
+ }
+ }
+ for i, v := range t.sources {
+ tuSources := append(sources, v)
+ out := t.stdout
+ if t.saveConfig != "" {
+ out = io.Discard
+ t.E = true
+ }
+ if t.E {
+ t.cfg.PreprocessOnly = true
+ if err := cc.Preprocess(t.cfg, includePaths, sysIncludePaths, tuSources, out); err != nil {
+ return err
+ }
+ memGuard(i, t.isScripted)
+ continue
+ }
+
+ var t0 time.Time
+ if t.traceTranslationUnits {
+ fmt.Printf("C front end %d/%d: %s ... ", i+1, len(t.sources), v.Name)
+ t0 = time.Now()
+ }
+ ast, err := cc.Translate(t.cfg, includePaths, sysIncludePaths, tuSources)
+ if err != nil {
+ return err
+ }
+
+ if t.traceTranslationUnits {
+ fmt.Println(time.Since(t0))
+ }
+ t.asts = append(t.asts, ast)
+ memGuard(i, t.isScripted)
+ }
+ if t.E || t.isScripted {
+ return nil
+ }
+
+ return t.link()
+}
+
+func (t *Task) configure() (err error) {
+ if t.configured {
+ return nil
+ }
+
+ type jsonConfig struct {
+ Predefined string
+ IncludePaths []string
+ SysIncludePaths []string
+ OS string
+ Arch string
+ }
+
+ t.configured = true
+ if t.loadConfig != "" {
+ path := filepath.Join(t.loadConfig, "config.json")
+ // trc("%p: LOAD_CONFIG(%s)", t, path)
+ b, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+
+ loadConfig := &jsonConfig{}
+ if err := json.Unmarshal(b, loadConfig); err != nil {
+ return err
+ }
+
+ t.goos = loadConfig.OS
+ t.goarch = loadConfig.Arch
+ for _, v := range loadConfig.IncludePaths {
+ t.hostIncludes = append(t.hostIncludes, filepath.Join(t.loadConfig, v))
+ }
+ for _, v := range loadConfig.SysIncludePaths {
+ t.hostSysIncludes = append(t.hostSysIncludes, filepath.Join(t.loadConfig, v))
+ }
+ t.hostPredefined = loadConfig.Predefined
+ return nil
+ }
+
+ hostConfigOpts := strings.Split(t.hostConfigOpts, ",")
+ if t.hostConfigOpts == "" {
+ hostConfigOpts = nil
+ }
+ if t.hostPredefined, t.hostIncludes, t.hostSysIncludes, err = cc.HostConfig(t.hostConfigCmd, hostConfigOpts...); err != nil {
+ return err
+ }
+
+ if t.saveConfig != "" && !t.configSaved {
+ t.configSaved = true
+ // trc("%p: SAVE_CONFIG(%s)", t, t.saveConfig)
+ cfg := &jsonConfig{
+ Predefined: t.hostPredefined,
+ IncludePaths: t.hostIncludes,
+ SysIncludePaths: t.hostSysIncludes,
+ OS: t.goos,
+ Arch: t.goarch,
+ }
+ b, err := json.Marshal(cfg)
+ if err != nil {
+ return err
+ }
+
+ full := filepath.Join(t.saveConfig, "config.json")
+ if err := os.MkdirAll(t.saveConfig, 0700); err != nil {
+ return err
+ }
+
+ if err := ioutil.WriteFile(full, b, 0600); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (t *Task) setLookPaths() (err error) {
+ if t.ccLookPath, err = exec.LookPath(t.cc); err != nil {
+ return err
+ }
+
+ t.arLookPath, err = exec.LookPath(t.ar)
+ return err
+}
+
+func (t *Task) link() (err error) {
+ if len(t.asts) == 0 {
+ return fmt.Errorf("no objects to link")
+ }
+
+ if t.o == "" {
+ t.o = fmt.Sprintf("a_%s_%s.go", t.goos, t.goarch)
+ }
+ dir := filepath.Dir(t.o)
+ t.capif = filepath.Join(dir, fmt.Sprintf("capi_%s_%s.go", t.goos, t.goarch))
+ f, err2 := os.Create(t.o)
+ if err2 != nil {
+ return err2
+ }
+
+ defer func() {
+ if e := f.Close(); e != nil && err == nil {
+ err = e
+ return
+ }
+
+ if out, e := exec.Command("gofmt", "-r", "(x) -> x", "-l", "-s", "-w", t.o).CombinedOutput(); e != nil && err == nil {
+ err = fmt.Errorf(strings.Join([]string{string(out), e.Error()}, ": "))
+ }
+ if out, e := exec.Command("gofmt", "-l", "-s", "-w", t.o).CombinedOutput(); e != nil && err == nil {
+ err = fmt.Errorf(strings.Join([]string{string(out), e.Error()}, ": "))
+ }
+ }()
+
+ w := bufio.NewWriter(f)
+
+ defer func() {
+ if e := w.Flush(); e != nil && err == nil {
+ err = e
+ }
+ }()
+
+ t.out = w
+ p, err := newProject(t)
+ if err != nil {
+ return err
+ }
+
+ return p.main()
+}
+
+func (t *Task) scriptBuild(fn string) error {
+ f, err := os.Open(fn)
+ if err != nil {
+ return err
+ }
+
+ defer f.Close()
+
+ r := csv.NewReader(f)
+ r.Comment = '#'
+ r.FieldsPerRecord = -1
+ r.TrimLeadingSpace = true
+ script, err := r.ReadAll()
+ if err != nil {
+ return err
+ }
+
+ return t.scriptBuild2(script)
+}
+
+func (t *Task) scriptBuild2(script [][]string) error {
+ var ldir string
+ ccgo := []string{t.args[0]}
+ for i, line := range script {
+ dir := line[0]
+ args := line[1:]
+ for _, v := range args {
+ if strings.HasSuffix(v, ".c") || strings.HasSuffix(v, ".h") {
+ v = filepath.Join(dir, v)
+ t.symSearchOrder = append(t.symSearchOrder, len(t.sources))
+ t.sources = append(t.sources, cc.Source{Name: v})
+ }
+ }
+ cmd := append(ccgo, args...)
+ if t.traceTranslationUnits {
+ if dir != ldir {
+ fmt.Println(dir)
+ ldir = dir
+ }
+ fmt.Printf("%s\n", cmd)
+ }
+ t2 := NewTask(append(ccgo, args...), t.stdout, t.stderr)
+ t2.cfg.IncludeFileHandler = t.cfg.IncludeFileHandler
+ t2.cfg.SharedFunctionDefinitions = t.cfg.SharedFunctionDefinitions
+ t2.configSaved = t.configSaved
+ t2.configured = t.configured
+ t2.hostIncludes = t.hostIncludes
+ t2.hostPredefined = t.hostPredefined
+ t2.hostSysIncludes = t.hostSysIncludes
+ t2.includedFiles = t.includedFiles
+ t2.isScripted = true
+ t2.loadConfig = t.loadConfig
+ t2.replaceFdZero = t.replaceFdZero
+ t2.replaceTclDefaultDoubleRounding = t.replaceTclDefaultDoubleRounding
+ t2.replaceTclIeeeDoubleRounding = t.replaceTclIeeeDoubleRounding
+ t2.saveConfig = t.saveConfig
+ if err := inDir(dir, t2.Main); err != nil {
+ return err
+ }
+
+ t.asts = append(t.asts, t2.asts...)
+ if i == 0 {
+ t.cfg = t2.cfg
+ }
+ }
+ if t.crtImportPath != "" {
+ t.l = append(t.l, t.crtImportPath)
+ t.symSearchOrder = append(t.symSearchOrder, -len(t.l))
+ m := map[string]struct{}{}
+ for _, v := range t.l {
+ v = strings.TrimSpace(v)
+ if _, ok := m[v]; !ok {
+ t.imported = append(t.imported, &imported{path: v})
+ m[v] = struct{}{}
+ }
+ }
+ t.imported[len(t.imported)-1].used = true // crt is always imported
+ }
+ if t.saveConfig != "" {
+ return nil
+ }
+
+ return t.link()
+}
+
+type cdb struct {
+ items []*cdbItem
+ outputIndex map[string][]*cdbItem
+}
+
+func (db *cdb) find(obj map[string]*cdbItem, nm string, ver, seqLimit int, path []string, cc, ar string, ignored map[string]struct{}) error {
+ // trc("%v: nm %q ver %v seqLimit %v path %q cc %q ar %q", origin(1), nm, ver, seqLimit, path, cc, ar)
+ var item *cdbItem
+ var k string
+ switch {
+ case ver < 0:
+ // find highest ver with .seq < seqLimit
+ for i, v := range db.outputIndex[nm] {
+ if v.seq >= seqLimit {
+ break
+ }
+
+ item = v
+ ver = i
+ }
+ if item == nil {
+ ver = -1
+ for _, v := range db.items {
+ if seqLimit >= 0 && v.seq >= seqLimit {
+ break
+ }
+
+ if filepath.Base(v.Output) == filepath.Base(nm) {
+ item = v
+ ver = v.ver
+ break
+ }
+ }
+ }
+
+ k = fmt.Sprintf("%s#%d", nm, ver)
+ default:
+ // match ver exactly
+ k = fmt.Sprintf("%s#%d", nm, ver)
+ if obj[k] != nil {
+ return nil
+ }
+
+ items := db.outputIndex[nm]
+ switch {
+ case ver < len(items):
+ panic(todo("", nm, ver, seqLimit))
+ default:
+ n := -1
+ for _, v := range db.items {
+ if seqLimit >= 0 && v.seq >= seqLimit {
+ break
+ }
+
+ if filepath.Base(v.Output) == filepath.Base(nm) {
+ n++
+ if n == ver {
+ item = v
+ break
+ }
+ }
+ }
+ }
+ }
+ if item == nil {
+ for k := range ignored {
+ if k == nm || strings.HasSuffix(nm, k) {
+ return nil
+ }
+ }
+
+ return fmt.Errorf("not found in compile DB: %s (max seq %d), path %v", k, seqLimit, path)
+ }
+
+ if obj[k] != nil {
+ return nil
+ }
+
+ obj[k] = item
+ var errs []string
+ for _, v := range item.sources(cc, ar) {
+ if err := db.find(obj, v, -1, item.seq, append(path, nm), cc, ar, ignored); err != nil {
+ errs = append(errs, err.Error())
+ }
+ }
+ if len(errs) != 0 {
+ sort.Strings(errs)
+ w := 0
+ for _, v := range errs {
+ if w == 0 || w > 0 && v != errs[w-1] {
+ errs[w] = v
+ w++
+ }
+ }
+ errs = errs[:w]
+ return fmt.Errorf("%s", strings.Join(errs, "\n"))
+ }
+
+ return nil
+}
+
+func suffixNum(s string, dflt int) (string, int) {
+ x := strings.LastIndexByte(s, '#')
+ if x < 0 {
+ return s, dflt
+ }
+
+ // foo#42
+ // 012345
+ // x == 3
+ num := s[x+1:]
+ n, err := strconv.ParseUint(num, 10, 32)
+ if err != nil {
+ return s, dflt
+ }
+
+ return s[:x], int(n)
+}
+
+func (t *Task) useCompileDB(fn string, args []string) error {
+ if err := t.setLookPaths(); err != nil {
+ return err
+ }
+
+ var cdb cdb
+ f, err := os.Open(fn)
+ if err != nil {
+ return err
+ }
+
+ de := json.NewDecoder(f)
+ err = de.Decode(&cdb.items)
+ f.Close()
+ if err != nil {
+ return err
+ }
+
+ cdb.outputIndex = map[string][]*cdbItem{}
+ for i, v := range cdb.items {
+ v.seq = i
+ if len(v.Arguments) == 0 {
+ if len(v.Command) == 0 {
+ return fmt.Errorf("either arguments or command is required: %+v", v)
+ }
+
+ if v.Arguments, err = shellquote.Split(v.Command); err != nil {
+ return err
+ }
+ }
+
+ k := v.output(t.ccLookPath, t.arLookPath)
+ a := cdb.outputIndex[k]
+ v.ver = len(a)
+ cdb.outputIndex[k] = append(a, v)
+ }
+ obj := map[string]*cdbItem{}
+ notFound := false
+ for _, v := range args {
+ v, ver := suffixNum(v, 0)
+ if err := cdb.find(obj, v, ver, -1, nil, t.ccLookPath, t.arLookPath, t.ignoredObjects); err != nil {
+ notFound = true
+ fmt.Fprintln(os.Stderr, err)
+ }
+ }
+ if notFound {
+ var a []string
+ for k, v := range cdb.outputIndex {
+ for _, w := range v {
+ a = append(a, fmt.Sprintf("%5d %s", w.seq, k))
+ }
+ }
+ sort.Strings(a)
+ fmt.Fprintf(os.Stderr, "compile DB index:\n\t%s\n", strings.Join(a, "\n\t"))
+ }
+
+ var a []string
+ for k := range obj {
+ a = append(a, k)
+ }
+ sort.Strings(a)
+ return t.cdbBuild(obj, a)
+}
+
+func (t *Task) cdbBuild(obj map[string]*cdbItem, list []string) error {
+ var script [][]string
+ for _, nm := range list {
+ it := obj[nm]
+ if !strings.HasSuffix(it.Output, ".o") || it.Arguments[0] != t.cc {
+ continue
+ }
+
+ args, err := it.ccgoArgs(t.cc)
+ if err != nil {
+ return err
+ }
+
+ for _, v := range t.D {
+ args = append(args, "-D"+v)
+ }
+ for _, v := range t.U {
+ args = append(args, "-U"+v)
+ }
+
+ line := append([]string{it.Directory}, args...)
+ script = append(script, line)
+ }
+ return t.scriptBuild2(script)
+}
+
+func (t *Task) createCompileDB(command []string) (rerr error) {
+ if err := t.setLookPaths(); err != nil {
+ return err
+ }
+
+ cwd, err := os.Getwd()
+ if err != nil {
+ return err
+ }
+
+ f, err := os.Create(t.compiledb)
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ if err := f.Close(); err != nil && rerr == nil {
+ rerr = err
+ }
+ }()
+
+ cwr := newCDBWriter(f)
+
+ defer func() {
+ if err := cwr.finish(); err != nil && rerr == nil {
+ rerr = err
+ }
+ }()
+
+ var cmd *exec.Cmd
+ var parser func(s string) ([]string, error)
+out:
+ switch t.goos {
+ case "darwin", "freebsd", "netbsd", "openbsd":
+ switch command[0] {
+ case "make", "gmake":
+ // ok
+ default:
+ return fmt.Errorf("usupported build command: %s", command[0])
+ }
+
+ sh, err := exec.LookPath("sh")
+ if err != nil {
+ return err
+ }
+
+ command = append([]string{sh, "-c"}, join(" ", command[0], "SHELL='sh -x'", command[1:]))
+ cmd = exec.Command(command[0], command[1:]...)
+ parser = makeXParser
+ case "windows":
+ if command[0] != "make" {
+ return fmt.Errorf("usupported build command: %s", command[0])
+ }
+
+ switch s := runtime.GOOS; s {
+ case "windows":
+ argv := append([]string{"-d"}, command[1:]...)
+ command[0] += ".exe"
+ cmd = exec.Command(command[0], argv...)
+ parser = makeDParser
+ break out
+ case "linux":
+ // ok
+ default:
+ return fmt.Errorf("usupported cross compile host: %s", s)
+ }
+
+ fallthrough
+ default:
+ strace, err := exec.LookPath("strace")
+ if err != nil {
+ return err
+ }
+
+ argv := append([]string{"-f", "-s1000000", "-e", "trace=execve"}, command...)
+ cmd = exec.Command(strace, argv...)
+ parser = straceParser
+ }
+ cmd.Env = append(os.Environ(), "LC_ALL=C")
+ cw := t.newCdbMakeWriter(cwr, cwd, parser)
+ switch {
+ case t.verboseCompiledb:
+ cmd.Stdout = io.MultiWriter(cw, os.Stdout)
+ default:
+ cmd.Stdout = cw
+ }
+ cmd.Stderr = cmd.Stdout
+ if dmesgs {
+ dmesg("%v: %v", origin(1), cmd.Args)
+ }
+ if err := cmd.Run(); err != nil {
+ if dmesgs {
+ dmesg("%v: cmd.Run: %v", origin(1), err)
+ }
+ return err
+ }
+
+ return cw.err
+}
+
+func makeDParser(s string) ([]string, error) {
+ const prefix = "CreateProcess("
+ if !strings.HasPrefix(s, prefix) {
+ return nil, nil
+ }
+
+ // s: `CreateProcess(C:\Program Files\CodeBlocks\MinGW\bin\gcc.exe,gcc -O3 -Wall -c -o compress.o compress.c,...)`
+ s = s[len(prefix):]
+ // s: `C:\Program Files\CodeBlocks\MinGW\bin\gcc.exe,gcc -O3 -Wall -c -o compress.o compress.c,...)`
+ x := strings.IndexByte(s, ',')
+ if x < 0 {
+ return nil, nil
+ }
+
+ cmd := s[:x]
+ // cmd: `C:\Program Files\CodeBlocks\MinGW\bin\gcc.exe`
+
+ s = s[x+1:]
+ // s: `gcc -O3 -Wall -c -o compress.o compress.c,...)`
+ if x = strings.LastIndexByte(s, ','); x < 0 {
+ return nil, nil
+ }
+
+ s = s[:x]
+ // s: `gcc -O3 -Wall -c -o compress.o compress.c`
+ a, err := shellquote.Split(strings.TrimSpace(s))
+ if err != nil || len(a) == 0 {
+ return nil, err
+ }
+
+ return append([]string{cmd}, a[1:]...), nil
+}
+
+func isCreateArchive(s string) bool {
+ // ar modifiers may be in any order so sort characters in s before checking.
+ // This turns eg `rc` into `cr`.
+ b := []byte(s)
+ sort.Slice(b, func(i, j int) bool { return b[i] < b[j] })
+ switch string(b) {
+ case "cq", "cr", "crs", "cru":
+ return true
+ }
+ return false
+}
+
+func hasPlusPrefix(s string) (n int, r string) {
+ for strings.HasPrefix(s, "+") {
+ n++
+ s = s[1:]
+ }
+ return n, s
+}
+
+func makeXParser(s string) (r []string, err error) {
+ switch {
+ case strings.HasPrefix(s, "libtool: link: ar "):
+ s = s[len("libtool: link:"):]
+ case strings.HasPrefix(s, "libtool: compile: "):
+ s = s[len("libtool: compile:"):]
+ for strings.HasPrefix(s, " ") {
+ s = s[1:]
+ }
+ default:
+ var n int
+ if n, s = hasPlusPrefix(s); n == 0 {
+ return nil, nil
+ }
+ }
+
+ if !strings.HasPrefix(s, " ") {
+ return nil, nil
+ }
+
+ s = s[1:]
+ r, err = shellquote.Split(s)
+ if err != nil {
+ if strings.Contains(err.Error(), "Unterminated single-quoted string") {
+ return nil, nil // ignore
+ }
+ }
+ if len(r) != 0 && filepath.Base(r[0]) == "libtool" {
+ r[0] = "libtool"
+ }
+ return r, err
+}
+
+func straceParser(s string) ([]string, error) {
+ prefix := "execve("
+ if strings.HasPrefix(s, "[pid ") {
+ s = strings.TrimSpace(s[strings.IndexByte(s, ']')+1:])
+ }
+ if !strings.HasPrefix(s, prefix) || !strings.HasSuffix(s, ") = 0") {
+ return nil, nil
+ }
+
+ // 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`
+ s = s[len(prefix):]
+ // 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`
+ a := strings.SplitN(s, ", [", 2)
+ // 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`
+ args := a[1]
+ // args: `"ar", "cr", "libtcl8.6.a", "regcomp.o", ... "bn_s_mp_sqr.o", "bn_s_mp_sub.o"], 0x55e6bbf49648 /* 60 vars */) = 0`
+ args = args[:strings.LastIndex(args, "], ")]
+ // args: `"ar", "cr", "libtcl8.6.a", "regcomp.o", ... "bn_s_mp_sqr.o", "bn_s_mp_sub.o"`
+ argv, err := shellquote.Split(args)
+ if err != nil {
+ return nil, err
+ }
+
+ words, err := shellquote.Split(a[0])
+ if err != nil {
+ return nil, err
+ }
+
+ argv[0] = words[0]
+ for i, v := range argv {
+ if strings.HasSuffix(v, ",") {
+ v = v[:len(v)-1]
+ }
+ if v2, err := strconv.Unquote(`"` + v + `"`); err == nil {
+ v = v2
+ }
+ argv[i] = v
+ }
+
+ return argv, nil
+}
+
+type cdbItem struct {
+ Arguments []string `json:"arguments"`
+ Command string `json:"command,omitempty"`
+ Directory string `json:"directory"`
+ File string `json:"file"`
+ Output string `json:"output,omitempty"`
+
+ seq int
+ ver int
+}
+
+func (it *cdbItem) cmpString() string { return fmt.Sprint(*it) }
+
+func (it *cdbItem) ccgoArgs(cc string) (r []string, err error) {
+ switch it.Arguments[0] {
+ case cc:
+ set := opt.NewSet()
+ set.Arg("D", true, func(opt, arg string) error { r = append(r, "-D"+arg); return nil })
+ set.Arg("I", true, func(opt, arg string) error { r = append(r, "-I"+arg); return nil })
+ set.Arg("MF", true, func(opt, arg string) error { return nil })
+ set.Arg("MT", true, func(opt, arg string) error { return nil })
+ set.Arg("O", true, func(opt, arg string) error { return nil })
+ set.Arg("U", true, func(opt, arg string) error { r = append(r, "-U"+arg); return nil })
+ set.Arg("o", true, func(opt, arg string) error { return nil })
+ set.Arg("std", true, func(opt, arg string) error { return nil })
+ set.Opt("MD", func(opt string) error { return nil })
+ set.Opt("MMD", func(opt string) error { return nil })
+ set.Opt("MP", func(opt string) error { return nil })
+ set.Opt("ansi", func(opt string) error { return nil })
+ set.Opt("c", func(opt string) error { return nil })
+ set.Opt("g", func(opt string) error { return nil })
+ set.Opt("pedantic", func(opt string) error { return nil })
+ set.Opt("pipe", func(opt string) error { return nil })
+ set.Opt("pthread", func(opt string) error { return nil })
+ set.Opt("s", func(opt string) error { return nil })
+ set.Opt("w", func(opt string) error { return nil })
+ if err := set.Parse(it.Arguments[1:], func(arg string) error {
+ switch {
+ case strings.HasSuffix(arg, ".c"):
+ r = append(r, arg)
+ case
+
+ strings.HasPrefix(arg, "-W"),
+ strings.HasPrefix(arg, "-f"),
+ strings.HasPrefix(arg, "-m"):
+
+ // nop
+ case strings.HasPrefix(arg, ">"):
+ return opt.Skip(nil)
+ default:
+ return fmt.Errorf("unknown/unsupported CC option: %s", arg)
+ }
+
+ return nil
+ }); err != nil {
+ switch err.(type) {
+ case opt.Skip:
+ // ok
+ default:
+ return nil, err
+ }
+ }
+
+ return r, nil
+ default:
+ return nil, fmt.Errorf("command not supported: %q", it.Arguments[0])
+ }
+}
+
+func (it *cdbItem) output(cc, ar string) (r string) {
+ if it.Output != "" {
+ return it.Output
+ }
+
+ if len(it.Arguments) == 0 {
+ return ""
+ }
+
+ switch it.Arguments[0] {
+ case cc:
+ for i, v := range it.Arguments {
+ if v == "-o" && i < len(it.Arguments)-1 {
+ it.Output = filepath.Join(it.Directory, it.Arguments[i+1])
+ break
+ }
+ }
+ if it.Output == "" && strings.HasSuffix(it.File, ".c") {
+ for _, v := range it.Arguments {
+ if v == "-c" {
+ bn := filepath.Base(it.File)
+ it.Output = filepath.Join(it.Directory, bn[:len(bn)-2]+".o")
+ break
+ }
+ }
+ }
+ case ar:
+ if isCreateArchive(it.Arguments[1]) {
+ it.Output = filepath.Join(it.Directory, it.Arguments[2])
+ }
+ case "libtool":
+ for i, v := range it.Arguments {
+ if v == "-o" && i < len(it.Arguments)-1 {
+ it.Output = filepath.Join(it.Directory, it.Arguments[i+1])
+ }
+ }
+ }
+ return it.Output
+}
+
+func (it *cdbItem) sources(cc, ar string) (r []string) {
+ if len(it.Arguments) == 0 {
+ return nil
+ }
+
+ switch arg0 := it.Arguments[0]; arg0 {
+ case
+ "libtool",
+ ar,
+ filepath.Base(ar),
+ cc:
+
+ var prev string
+ for _, v := range it.Arguments {
+ switch prev {
+ case "-o", "-MT", "-MF":
+ // nop
+ default:
+ if strings.HasSuffix(v, ".o") {
+ r = append(r, filepath.Join(it.Directory, v))
+ }
+ }
+ prev = v
+ }
+ return r
+ default:
+ panic(todo("cc: %q ar: %q it: %+v", cc, ar, it))
+ }
+}
+
+type cdbMakeWriter struct {
+ b bytes.Buffer
+ cc string
+ ar string
+ arBase string
+ dir string
+ err error
+ it cdbItem
+ parser func(s string) ([]string, error)
+ sc *bufio.Scanner
+ w *cdbWriter
+}
+
+func (t *Task) newCdbMakeWriter(w *cdbWriter, dir string, parser func(s string) ([]string, error)) *cdbMakeWriter {
+ const sz = 1 << 16
+ r := &cdbMakeWriter{
+ cc: t.ccLookPath,
+ ar: t.arLookPath,
+ arBase: filepath.Base(t.arLookPath),
+ dir: dir,
+ parser: parser,
+ w: w,
+ }
+ r.sc = bufio.NewScanner(&r.b)
+ r.sc.Buffer(make([]byte, sz), sz)
+ return r
+}
+
+func (w *cdbMakeWriter) fail(err error) {
+ if w.err == nil {
+ w.err = fmt.Errorf("%v (%v)", err, origin(2))
+ }
+}
+
+func (w *cdbMakeWriter) Write(b []byte) (int, error) {
+ w.b.Write(b)
+ for bytes.Contains(w.b.Bytes(), []byte{'\n'}) {
+ if !w.sc.Scan() {
+ panic(todo("internal error"))
+ }
+
+ s := strings.TrimSpace(w.sc.Text())
+ if edx := strings.Index(s, "Entering directory"); edx >= 0 {
+ s = s[edx+len("Entering directory"):]
+ s = strings.TrimSpace(s)
+ if len(s) == 0 {
+ continue
+ }
+
+ if (s[0] == '\'' || s[0] == '`') && s[len(s)-1] == '\'' {
+ s = s[1:]
+ if len(s) == 0 {
+ continue
+ }
+
+ s = s[:len(s)-1]
+ }
+ s = `"` + s + `"`
+ dir, err := strconv.Unquote(s)
+ if err != nil {
+ w.fail(err)
+ continue
+ }
+
+ dir = filepath.Clean(dir)
+ if dir == w.dir {
+ continue
+ }
+
+ w.dir = dir
+ fmt.Printf("cd %s\n", dir)
+ continue
+ }
+
+ args, err := w.parser(s)
+ if err != nil {
+ w.fail(err)
+ continue
+ }
+
+ if len(args) == 0 {
+ continue
+ }
+
+ // TODO: change so eg handleGCC returns []cdbItem, skip if none.
+
+ w.it = cdbItem{}
+
+ err = nil
+ switch args[0] {
+ case w.cc:
+ fmt.Printf("CCGO CC: %q\n", args)
+ err = w.handleGCC(args)
+ case w.ar:
+ fallthrough
+ case w.arBase:
+ if isCreateArchive(args[1]) {
+ fmt.Printf("CCGO AR: %q\n", args)
+ err = w.handleAR(args)
+ }
+ case "libtool":
+ fmt.Printf("CCGO LIBTOOL: %q\n", args)
+ err = w.handleLibtool(args)
+ }
+ if err != nil {
+ w.fail(err)
+ continue
+ }
+
+ if w.it.Output != "" {
+ w.w.add(w.it)
+ }
+ }
+ return len(b), nil
+}
+
+func (w *cdbMakeWriter) handleLibtool(args []string) error {
+ w.it = cdbItem{
+ Arguments: args,
+ Directory: w.dir,
+ }
+ for i, v := range args {
+ switch {
+ case v == "-o" && i < len(args)-1:
+ w.it.Output = filepath.Join(w.dir, args[i+1])
+ }
+ }
+ w.it.output(w.cc, w.ar)
+ return nil
+}
+
+func (w *cdbMakeWriter) handleAR(args []string) error {
+ w.it = cdbItem{
+ Arguments: args,
+ Directory: w.dir,
+ }
+ // TODO: assumes isCreateArchive has already been checked
+ w.it.Output = filepath.Join(w.dir, args[2])
+ return nil
+}
+
+func (w *cdbMakeWriter) handleGCC(args []string) error {
+ w.it = cdbItem{
+ Arguments: args,
+ Directory: w.dir,
+ }
+ for i, v := range args {
+ switch {
+ case v == "-o" && i < len(args)-1:
+ w.it.Output = filepath.Join(w.dir, args[i+1])
+ case strings.HasSuffix(v, ".c"):
+ if w.it.File != "" {
+ return fmt.Errorf("multiple .c files: %s", v)
+ }
+
+ w.it.File = filepath.Clean(v)
+ }
+ }
+ w.it.output(w.cc, w.ar)
+ return nil
+}
+
+type cdbWriter struct {
+ w *bufio.Writer
+ items []cdbItem
+}
+
+func newCDBWriter(w io.Writer) *cdbWriter {
+ return &cdbWriter{w: bufio.NewWriter(w)}
+}
+
+func (w *cdbWriter) add(item cdbItem) {
+ w.items = append(w.items, item)
+}
+
+func (w *cdbWriter) finish() error {
+ enc := json.NewEncoder(w.w)
+ enc.SetIndent("", " ")
+ if err := enc.Encode(w.items); err != nil {
+ return err
+ }
+ return w.w.Flush()
+}
+
+func join(sep string, a ...interface{}) string {
+ var b []string
+ for _, v := range a {
+ switch x := v.(type) {
+ case string:
+ b = append(b, x)
+ case []string:
+ b = append(b, x...)
+ default:
+ panic(todo("internal error: %T", x))
+ }
+ }
+ return strings.Join(b, sep)
+}
+
+func inDir(dir string, f func() error) (err error) {
+ var cwd string
+ if cwd, err = os.Getwd(); err != nil {
+ return err
+ }
+
+ defer func() {
+ if err2 := os.Chdir(cwd); err2 != nil {
+ err = err2
+ }
+ }()
+
+ if err = os.Chdir(dir); err != nil {
+ return err
+ }
+
+ return f()
+}
+
+func detectMingw(s string) bool {
+ return strings.Contains(s, "#define __MINGW")
+}
+
+func memGuard(i int, force bool) {
+ if totalRam == 0 || totalRam > 64e9 {
+ return
+ }
+
+ var ms runtime.MemStats
+ runtime.ReadMemStats(&ms)
+ switch {
+ case ms.Alloc < totalRam/2:
+ return
+ case ms.Alloc < (8*totalRam)/10:
+ if force {
+ break
+ }
+
+ switch {
+ case totalRam < 1e9:
+ // ok
+ case totalRam < 16e9:
+ if i&1 == 1 {
+ return
+ }
+ default:
+ if i&3 != 3 {
+ return
+ }
+ }
+ }
+
+ debug.FreeOSMemory()
+}