github.com/aca02djr/gb@v0.4.1/cgo.go (about)

     1  package gb
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  )
    14  
    15  func cgo(pkg *Package) (*Action, []string, []string, error) {
    16  	switch {
    17  	case goversion == 1.4:
    18  		return cgo14(pkg)
    19  	case goversion > 1.4:
    20  		return cgo15(pkg)
    21  	default:
    22  		return nil, nil, nil, fmt.Errorf("unsupported Go version: %v", runtime.Version)
    23  	}
    24  }
    25  
    26  // cgo produces a an Action representing the cgo steps
    27  // an ofile representing the result of the cgo steps
    28  // a set of .go files for compilation, and an error.
    29  func cgo14(pkg *Package) (*Action, []string, []string, error) {
    30  
    31  	// collect cflags and ldflags from the package
    32  	// the environment, and pkg-config.
    33  	cgoCPPFLAGS, cgoCFLAGS, cgoCXXFLAGS, cgoLDFLAGS := cflags(pkg, false)
    34  	pcCFLAGS, pcLDFLAGS, err := pkgconfig(pkg)
    35  	if err != nil {
    36  		return nil, nil, nil, err
    37  	}
    38  	cgoCFLAGS = append(cgoCFLAGS, pcCFLAGS...)
    39  	cgoLDFLAGS = append(cgoLDFLAGS, pcLDFLAGS...)
    40  
    41  	runcgo1 := []*Action{
    42  		&Action{
    43  			Name: "runcgo1: " + pkg.ImportPath,
    44  			Run:  func() error { return runcgo1(pkg, cgoCFLAGS, cgoLDFLAGS) },
    45  		}}
    46  
    47  	workdir := cgoworkdir(pkg)
    48  	defun := filepath.Join(workdir, "_cgo_defun.o")
    49  	rundefun := Action{
    50  		Name: "cc: " + pkg.ImportPath + ": _cgo_defun_c",
    51  		Deps: runcgo1,
    52  		Run:  func() error { return pkg.tc.Cc(pkg, defun, filepath.Join(workdir, "_cgo_defun.c")) },
    53  	}
    54  
    55  	cgofiles := []string{filepath.Join(workdir, "_cgo_gotypes.go")}
    56  	for _, f := range pkg.CgoFiles {
    57  		cgofiles = append(cgofiles, filepath.Join(workdir, stripext(f)+".cgo1.go"))
    58  	}
    59  	cfiles := []string{
    60  		filepath.Join(workdir, "_cgo_main.c"),
    61  		filepath.Join(workdir, "_cgo_export.c"),
    62  	}
    63  	cfiles = append(cfiles, pkg.CFiles...)
    64  
    65  	for _, f := range pkg.CgoFiles {
    66  		cfiles = append(cfiles, filepath.Join(workdir, stripext(f)+".cgo2.c"))
    67  	}
    68  
    69  	cflags := append(cgoCPPFLAGS, cgoCFLAGS...)
    70  	cxxflags := append(cgoCPPFLAGS, cgoCXXFLAGS...)
    71  	gcc1, ofiles := cgocc(pkg, cflags, cxxflags, cfiles, pkg.CXXFiles, runcgo1...)
    72  	ofiles = append(ofiles, pkg.SysoFiles...)
    73  	ofile := filepath.Join(filepath.Dir(ofiles[0]), "_cgo_.o")
    74  	gcc2 := Action{
    75  		Name: "gccld: " + pkg.ImportPath + ": _cgo_.o",
    76  		Deps: gcc1,
    77  		Run:  func() error { return gccld(pkg, cgoCFLAGS, cgoLDFLAGS, ofile, ofiles) },
    78  	}
    79  
    80  	dynout := filepath.Join(workdir, "_cgo_import.c")
    81  	imports := stripext(dynout) + ".o"
    82  	runcgo2 := Action{
    83  		Name: "runcgo2: " + pkg.ImportPath,
    84  		Deps: []*Action{&gcc2},
    85  		Run: func() error {
    86  			if err := runcgo2(pkg, dynout, ofile); err != nil {
    87  				return err
    88  			}
    89  			return pkg.tc.Cc(pkg, imports, dynout)
    90  		},
    91  	}
    92  
    93  	allo := filepath.Join(filepath.Dir(ofiles[0]), "_all.o")
    94  	action := Action{
    95  		Name: "rungcc3: " + pkg.ImportPath,
    96  		Deps: []*Action{&runcgo2, &rundefun},
    97  		Run: func() error {
    98  			return rungcc3(pkg, pkg.Dir, allo, ofiles[1:]) // skip _cgo_main.o
    99  		},
   100  	}
   101  	return &action, []string{defun, imports, allo}, cgofiles, nil
   102  }
   103  
   104  // cgo produces a an Action representing the cgo steps
   105  // an ofile representing the result of the cgo steps
   106  // a set of .go files for compilation, and an error.
   107  func cgo15(pkg *Package) (*Action, []string, []string, error) {
   108  
   109  	// collect cflags and ldflags from the package
   110  	// the environment, and pkg-config.
   111  	cgoCPPFLAGS, cgoCFLAGS, cgoCXXFLAGS, cgoLDFLAGS := cflags(pkg, false)
   112  	pcCFLAGS, pcLDFLAGS, err := pkgconfig(pkg)
   113  	if err != nil {
   114  		return nil, nil, nil, err
   115  	}
   116  	cgoCFLAGS = append(cgoCFLAGS, pcCFLAGS...)
   117  	cgoLDFLAGS = append(cgoLDFLAGS, pcLDFLAGS...)
   118  
   119  	runcgo1 := []*Action{
   120  		&Action{
   121  			Name: "runcgo1: " + pkg.ImportPath,
   122  			Run:  func() error { return runcgo1(pkg, cgoCFLAGS, cgoLDFLAGS) },
   123  		},
   124  	}
   125  
   126  	workdir := cgoworkdir(pkg)
   127  	cgofiles := []string{filepath.Join(workdir, "_cgo_gotypes.go")}
   128  	for _, f := range pkg.CgoFiles {
   129  		cgofiles = append(cgofiles, filepath.Join(workdir, stripext(f)+".cgo1.go"))
   130  	}
   131  	cfiles := []string{
   132  		filepath.Join(workdir, "_cgo_main.c"),
   133  		filepath.Join(workdir, "_cgo_export.c"),
   134  	}
   135  	cfiles = append(cfiles, pkg.CFiles...)
   136  
   137  	for _, f := range pkg.CgoFiles {
   138  		cfiles = append(cfiles, filepath.Join(workdir, stripext(f)+".cgo2.c"))
   139  	}
   140  
   141  	cflags := append(cgoCPPFLAGS, cgoCFLAGS...)
   142  	cxxflags := append(cgoCPPFLAGS, cgoCXXFLAGS...)
   143  	gcc1, ofiles := cgocc(pkg, cflags, cxxflags, cfiles, pkg.CXXFiles, runcgo1...)
   144  	ofiles = append(ofiles, pkg.SysoFiles...)
   145  	ofile := filepath.Join(filepath.Dir(ofiles[0]), "_cgo_.o")
   146  	gcc2 := Action{
   147  		Name: "gccld: " + pkg.ImportPath + ": _cgo_.o",
   148  		Deps: gcc1,
   149  		Run:  func() error { return gccld(pkg, cgoCFLAGS, cgoLDFLAGS, ofile, ofiles) },
   150  	}
   151  
   152  	dynout := filepath.Join(workdir, "_cgo_import.go")
   153  	runcgo2 := Action{
   154  		Name: "runcgo2: " + pkg.ImportPath,
   155  		Deps: []*Action{&gcc2},
   156  		Run:  func() error { return runcgo2(pkg, dynout, ofile) },
   157  	}
   158  	cgofiles = append(cgofiles, dynout)
   159  
   160  	allo := filepath.Join(filepath.Dir(ofiles[0]), "_all.o")
   161  	action := Action{
   162  		Name: "rungcc3: " + pkg.ImportPath,
   163  		Deps: []*Action{&runcgo2},
   164  		Run: func() error {
   165  			return rungcc3(pkg, pkg.Dir, allo, ofiles[1:]) // skip _cgo_main.o
   166  		},
   167  	}
   168  
   169  	return &action, []string{allo}, cgofiles, nil
   170  }
   171  
   172  // cgocc compiles all .c files.
   173  // TODO(dfc) cxx not done
   174  func cgocc(pkg *Package, cflags, cxxflags, cfiles, cxxfiles []string, deps ...*Action) ([]*Action, []string) {
   175  	workdir := cgoworkdir(pkg)
   176  	var cc []*Action
   177  	var ofiles []string
   178  	for _, cfile := range cfiles {
   179  		cfile := cfile
   180  		ofile := filepath.Join(workdir, stripext(filepath.Base(cfile))+".o")
   181  		ofiles = append(ofiles, ofile)
   182  		cc = append(cc, &Action{
   183  			Name: "rungcc1: " + pkg.ImportPath + ": " + cfile,
   184  			Deps: deps,
   185  			Run:  func() error { return rungcc1(pkg, cflags, ofile, cfile) },
   186  		})
   187  	}
   188  
   189  	for _, cxxfile := range cxxfiles {
   190  		cxxfile := cxxfile
   191  		ofile := filepath.Join(workdir, stripext(filepath.Base(cxxfile))+".o")
   192  		ofiles = append(ofiles, ofile)
   193  		cc = append(cc, &Action{
   194  			Name: "rung++1: " + pkg.ImportPath + ": " + cxxfile,
   195  			Deps: deps,
   196  			Run:  func() error { return rungpp1(pkg, cxxflags, ofile, cxxfile) },
   197  		})
   198  	}
   199  
   200  	return cc, ofiles
   201  }
   202  
   203  // rungcc1 invokes gcc to compile cfile into ofile
   204  func rungcc1(pkg *Package, cgoCFLAGS []string, ofile, cfile string) error {
   205  	args := []string{"-g", "-O2",
   206  		"-I", pkg.Dir,
   207  		"-I", filepath.Dir(ofile),
   208  	}
   209  	args = append(args, cgoCFLAGS...)
   210  	args = append(args,
   211  		"-o", ofile,
   212  		"-c", cfile,
   213  	)
   214  	t0 := time.Now()
   215  	gcc := gccCmd(pkg, pkg.Dir)
   216  	var buf bytes.Buffer
   217  	err := runOut(&buf, pkg.Dir, nil, gcc[0], append(gcc[1:], args...)...)
   218  	if err != nil {
   219  		fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath)
   220  		io.Copy(os.Stderr, &buf)
   221  	}
   222  	pkg.Record(gcc[0], time.Since(t0))
   223  	return err
   224  }
   225  
   226  // rungpp1 invokes g++ to compile cfile into ofile
   227  func rungpp1(pkg *Package, cgoCFLAGS []string, ofile, cfile string) error {
   228  	args := []string{"-g", "-O2",
   229  		"-I", pkg.Dir,
   230  		"-I", filepath.Dir(ofile),
   231  	}
   232  	args = append(args, cgoCFLAGS...)
   233  	args = append(args,
   234  		"-o", ofile,
   235  		"-c", cfile,
   236  	)
   237  	t0 := time.Now()
   238  	gxx := gxxCmd(pkg, pkg.Dir)
   239  	var buf bytes.Buffer
   240  	err := runOut(&buf, pkg.Dir, nil, gxx[0], append(gxx[1:], args...)...)
   241  	if err != nil {
   242  		fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath)
   243  		io.Copy(os.Stderr, &buf)
   244  	}
   245  	pkg.Record(gxx[0], time.Since(t0))
   246  	return err
   247  }
   248  
   249  // gccld links the o files from rungcc1 into a single _cgo_.o.
   250  func gccld(pkg *Package, cgoCFLAGS, cgoLDFLAGS []string, ofile string, ofiles []string) error {
   251  	args := []string{}
   252  	args = append(args, "-o", ofile)
   253  	args = append(args, ofiles...)
   254  	args = append(args, cgoLDFLAGS...) // this has to go at the end, because reasons!
   255  	t0 := time.Now()
   256  
   257  	var cmd []string
   258  	if len(pkg.CXXFiles) > 0 || len(pkg.SwigCXXFiles) > 0 {
   259  		cmd = gxxCmd(pkg, pkg.Dir)
   260  	} else {
   261  		cmd = gccCmd(pkg, pkg.Dir)
   262  	}
   263  	var buf bytes.Buffer
   264  	err := runOut(&buf, pkg.Dir, nil, cmd[0], append(cmd[1:], args...)...)
   265  	if err != nil {
   266  		fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath)
   267  		io.Copy(os.Stderr, &buf)
   268  	}
   269  	pkg.Record("gccld", time.Since(t0))
   270  	return err
   271  }
   272  
   273  // rungcc3 links all previous ofiles together with libgcc into a single _all.o.
   274  func rungcc3(pkg *Package, dir string, ofile string, ofiles []string) error {
   275  	args := []string{}
   276  	args = append(args, "-o", ofile)
   277  	args = append(args, ofiles...)
   278  	args = append(args, "-Wl,-r", "-nostdlib")
   279  	var cmd []string
   280  	if len(pkg.CXXFiles) > 0 || len(pkg.SwigCXXFiles) > 0 {
   281  		cmd = gxxCmd(pkg, dir)
   282  	} else {
   283  		cmd = gccCmd(pkg, dir)
   284  	}
   285  	if !strings.HasPrefix(cmd[0], "clang") {
   286  		libgcc, err := libgcc(pkg.Context)
   287  		if err != nil {
   288  			return nil
   289  		}
   290  		args = append(args, libgcc)
   291  	}
   292  	t0 := time.Now()
   293  	var buf bytes.Buffer
   294  	err := runOut(&buf, dir, nil, cmd[0], append(cmd[1:], args...)...)
   295  	if err != nil {
   296  		fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath)
   297  		io.Copy(os.Stderr, &buf)
   298  	}
   299  	pkg.Record("gcc3", time.Since(t0))
   300  	return err
   301  }
   302  
   303  // libgcc returns the value of gcc -print-libgcc-file-name.
   304  func libgcc(ctx *Context) (string, error) {
   305  	args := []string{
   306  		"-print-libgcc-file-name",
   307  	}
   308  	var buf bytes.Buffer
   309  	cmd := gccCmd(&Package{Context: ctx}, "") // TODO(dfc) hack
   310  	err := runOut(&buf, ".", nil, cmd[0], args...)
   311  	return strings.TrimSpace(buf.String()), err
   312  }
   313  
   314  func cgotool(ctx *Context) string {
   315  	return filepath.Join(runtime.GOROOT(), "pkg", "tool", ctx.gohostos+"_"+ctx.gohostarch, "cgo")
   316  }
   317  
   318  // envList returns the value of the given environment variable broken
   319  // into fields, using the default value when the variable is empty.
   320  func envList(key, def string) []string {
   321  	v := os.Getenv(key)
   322  	if v == "" {
   323  		v = def
   324  	}
   325  	return strings.Fields(v)
   326  }
   327  
   328  // Return the flags to use when invoking the C or C++ compilers, or cgo.
   329  func cflags(p *Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) {
   330  	var defaults string
   331  	if def {
   332  		defaults = "-g -O2"
   333  	}
   334  
   335  	cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS)
   336  	cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS)
   337  	cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS)
   338  	ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS)
   339  	return
   340  }
   341  
   342  // call pkg-config and return the cflags and ldflags.
   343  func pkgconfig(p *Package) ([]string, []string, error) {
   344  	if len(p.CgoPkgConfig) == 0 {
   345  		return nil, nil, nil // nothing to do
   346  	}
   347  	args := []string{
   348  		"--cflags",
   349  	}
   350  	args = append(args, p.CgoPkgConfig...)
   351  	var out bytes.Buffer
   352  	err := runOut(&out, p.Dir, nil, "pkg-config", args...)
   353  	if err != nil {
   354  		return nil, nil, err
   355  	}
   356  	cflags := strings.Fields(out.String())
   357  
   358  	args = []string{
   359  		"--libs",
   360  	}
   361  	args = append(args, p.CgoPkgConfig...)
   362  	out.Reset()
   363  	err = runOut(&out, p.Dir, nil, "pkg-config", args...)
   364  	if err != nil {
   365  		return nil, nil, err
   366  	}
   367  	ldflags := strings.Fields(out.String())
   368  	return cflags, ldflags, nil
   369  }
   370  
   371  func quoteFlags(flags []string) []string {
   372  	quoted := make([]string, len(flags))
   373  	for i, f := range flags {
   374  		quoted[i] = strconv.Quote(f)
   375  	}
   376  	return quoted
   377  }
   378  
   379  // runcgo1 invokes the cgo tool to process pkg.CgoFiles.
   380  func runcgo1(pkg *Package, cflags, ldflags []string) error {
   381  	cgo := cgotool(pkg.Context)
   382  	workdir := cgoworkdir(pkg)
   383  	if err := mkdir(workdir); err != nil {
   384  		return err
   385  	}
   386  
   387  	args := []string{"-objdir", workdir}
   388  	switch {
   389  	case goversion == 1.4:
   390  		args = append(args,
   391  			"--",
   392  			"-I", pkg.Dir,
   393  		)
   394  	case goversion > 1.4:
   395  		args = append(args,
   396  			"-importpath", pkg.ImportPath,
   397  			"--",
   398  			"-I", workdir,
   399  			"-I", pkg.Dir,
   400  		)
   401  	default:
   402  		return fmt.Errorf("unsupported Go version: %v", runtime.Version)
   403  	}
   404  	args = append(args, cflags...)
   405  	args = append(args, pkg.CgoFiles...)
   406  
   407  	cgoenv := []string{
   408  		"CGO_CFLAGS=" + strings.Join(quoteFlags(cflags), " "),
   409  		"CGO_LDFLAGS=" + strings.Join(quoteFlags(ldflags), " "),
   410  	}
   411  	var buf bytes.Buffer
   412  	err := runOut(&buf, pkg.Dir, cgoenv, cgo, args...)
   413  	if err != nil {
   414  		fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath)
   415  		io.Copy(os.Stderr, &buf)
   416  	}
   417  	return err
   418  }
   419  
   420  // runcgo2 invokes the cgo tool to create _cgo_import.go
   421  func runcgo2(pkg *Package, dynout, ofile string) error {
   422  	cgo := cgotool(pkg.Context)
   423  	workdir := cgoworkdir(pkg)
   424  
   425  	args := []string{
   426  		"-objdir", workdir,
   427  	}
   428  	switch {
   429  	case goversion == 1.4:
   430  		args = append(args,
   431  			"-dynimport", ofile,
   432  			"-dynout", dynout,
   433  		)
   434  	case goversion > 1.4:
   435  		args = append(args,
   436  			"-dynpackage", pkg.Name,
   437  			"-dynimport", ofile,
   438  			"-dynout", dynout,
   439  		)
   440  	default:
   441  		return fmt.Errorf("unsuppored Go version: %v", runtime.Version)
   442  	}
   443  	var buf bytes.Buffer
   444  	err := runOut(&buf, pkg.Dir, nil, cgo, args...)
   445  	if err != nil {
   446  		fmt.Fprintf(os.Stderr, "# %s\n", pkg.ImportPath)
   447  		io.Copy(os.Stderr, &buf)
   448  	}
   449  	return err
   450  }
   451  
   452  // cgoworkdir returns the cgo working directory for this package.
   453  func cgoworkdir(pkg *Package) string {
   454  	return filepath.Join(Workdir(pkg), pkgname(pkg), "_cgo")
   455  }
   456  
   457  // gccCmd returns a gcc command line prefix.
   458  func gccCmd(pkg *Package, objdir string) []string {
   459  	return ccompilerCmd(pkg, "CC", defaultCC, objdir)
   460  }
   461  
   462  // gxxCmd returns a g++ command line prefix.
   463  func gxxCmd(pkg *Package, objdir string) []string {
   464  	return ccompilerCmd(pkg, "CXX", defaultCXX, objdir)
   465  }
   466  
   467  // ccompilerCmd returns a command line prefix for the given environment
   468  // variable and using the default command when the variable is empty.
   469  func ccompilerCmd(pkg *Package, envvar, defcmd, objdir string) []string {
   470  	compiler := envList(envvar, defcmd)
   471  	a := []string{compiler[0]}
   472  	if objdir != "" {
   473  		a = append(a, "-I", objdir)
   474  	}
   475  	a = append(a, compiler[1:]...)
   476  
   477  	// Definitely want -fPIC but on Windows gcc complains
   478  	// "-fPIC ignored for target (all code is position independent)"
   479  	if pkg.gotargetos != "windows" {
   480  		a = append(a, "-fPIC")
   481  	}
   482  	a = append(a, gccArchArgs(pkg.gotargetarch)...)
   483  	// gcc-4.5 and beyond require explicit "-pthread" flag
   484  	// for multithreading with pthread library.
   485  	switch pkg.gotargetos {
   486  	case "windows":
   487  		a = append(a, "-mthreads")
   488  	default:
   489  		a = append(a, "-pthread")
   490  	}
   491  
   492  	if strings.Contains(a[0], "clang") {
   493  		// disable ASCII art in clang errors, if possible
   494  		a = append(a, "-fno-caret-diagnostics")
   495  		// clang is too smart about command-line arguments
   496  		a = append(a, "-Qunused-arguments")
   497  	}
   498  
   499  	// disable word wrapping in error messages
   500  	a = append(a, "-fmessage-length=0")
   501  
   502  	// On OS X, some of the compilers behave as if -fno-common
   503  	// is always set, and the Mach-O linker in 6l/8l assumes this.
   504  	// See https://golang.org/issue/3253.
   505  	if pkg.gotargetos == "darwin" {
   506  		a = append(a, "-fno-common")
   507  	}
   508  
   509  	return a
   510  }
   511  
   512  // linkCmd returns the name of the binary to use for linking for the given
   513  // environment variable and using the default command when the variable is
   514  // empty.
   515  func linkCmd(pkg *Package, envvar, defcmd string) string {
   516  	compiler := envList(envvar, defcmd)
   517  	return compiler[0]
   518  }
   519  
   520  // gccArchArgs returns arguments to pass to gcc based on the architecture.
   521  func gccArchArgs(goarch string) []string {
   522  	switch goarch {
   523  	case "386":
   524  		return []string{"-m32"}
   525  	case "amd64", "amd64p32":
   526  		return []string{"-m64"}
   527  	case "arm":
   528  		return []string{"-marm"} // not thumb
   529  	}
   530  	return nil
   531  }