github.com/kekek/gb@v0.4.5-0.20170222120241-d4ba64b0b297/cgo.go (about)

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