cgo.go

  1// Copyright 2013 The Go Authors. All rights reserved.
  2// Use of this source code is governed by a BSD-style
  3// license that can be found in the LICENSE file.
  4
  5package cgo
  6
  7// This file handles cgo preprocessing of files containing `import "C"`.
  8//
  9// DESIGN
 10//
 11// The approach taken is to run the cgo processor on the package's
 12// CgoFiles and parse the output, faking the filenames of the
 13// resulting ASTs so that the synthetic file containing the C types is
 14// called "C" (e.g. "~/go/src/net/C") and the preprocessed files
 15// have their original names (e.g. "~/go/src/net/cgo_unix.go"),
 16// not the names of the actual temporary files.
 17//
 18// The advantage of this approach is its fidelity to 'go build'.  The
 19// downside is that the token.Position.Offset for each AST node is
 20// incorrect, being an offset within the temporary file.  Line numbers
 21// should still be correct because of the //line comments.
 22//
 23// The logic of this file is mostly plundered from the 'go build'
 24// tool, which also invokes the cgo preprocessor.
 25//
 26//
 27// REJECTED ALTERNATIVE
 28//
 29// An alternative approach that we explored is to extend go/types'
 30// Importer mechanism to provide the identity of the importing package
 31// so that each time `import "C"` appears it resolves to a different
 32// synthetic package containing just the objects needed in that case.
 33// The loader would invoke cgo but parse only the cgo_types.go file
 34// defining the package-level objects, discarding the other files
 35// resulting from preprocessing.
 36//
 37// The benefit of this approach would have been that source-level
 38// syntax information would correspond exactly to the original cgo
 39// file, with no preprocessing involved, making source tools like
 40// godoc, guru, and eg happy.  However, the approach was rejected
 41// due to the additional complexity it would impose on go/types.  (It
 42// made for a beautiful demo, though.)
 43//
 44// cgo files, despite their *.go extension, are not legal Go source
 45// files per the specification since they may refer to unexported
 46// members of package "C" such as C.int.  Also, a function such as
 47// C.getpwent has in effect two types, one matching its C type and one
 48// which additionally returns (errno C.int).  The cgo preprocessor
 49// uses name mangling to distinguish these two functions in the
 50// processed code, but go/types would need to duplicate this logic in
 51// its handling of function calls, analogous to the treatment of map
 52// lookups in which y=m[k] and y,ok=m[k] are both legal.
 53
 54import (
 55	"fmt"
 56	"go/ast"
 57	"go/build"
 58	"go/parser"
 59	"go/token"
 60	"io/ioutil"
 61	"log"
 62	"os"
 63	"os/exec"
 64	"path/filepath"
 65	"regexp"
 66	"strings"
 67)
 68
 69// ProcessFiles invokes the cgo preprocessor on bp.CgoFiles, parses
 70// the output and returns the resulting ASTs.
 71//
 72func ProcessFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) {
 73	tmpdir, err := ioutil.TempDir("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C")
 74	if err != nil {
 75		return nil, err
 76	}
 77	defer os.RemoveAll(tmpdir)
 78
 79	pkgdir := bp.Dir
 80	if DisplayPath != nil {
 81		pkgdir = DisplayPath(pkgdir)
 82	}
 83
 84	cgoFiles, cgoDisplayFiles, err := Run(bp, pkgdir, tmpdir, false)
 85	if err != nil {
 86		return nil, err
 87	}
 88	var files []*ast.File
 89	for i := range cgoFiles {
 90		rd, err := os.Open(cgoFiles[i])
 91		if err != nil {
 92			return nil, err
 93		}
 94		display := filepath.Join(bp.Dir, cgoDisplayFiles[i])
 95		f, err := parser.ParseFile(fset, display, rd, mode)
 96		rd.Close()
 97		if err != nil {
 98			return nil, err
 99		}
100		files = append(files, f)
101	}
102	return files, nil
103}
104
105var cgoRe = regexp.MustCompile(`[/\\:]`)
106
107// Run invokes the cgo preprocessor on bp.CgoFiles and returns two
108// lists of files: the resulting processed files (in temporary
109// directory tmpdir) and the corresponding names of the unprocessed files.
110//
111// Run is adapted from (*builder).cgo in
112// $GOROOT/src/cmd/go/build.go, but these features are unsupported:
113// Objective C, CGOPKGPATH, CGO_FLAGS.
114//
115// If useabs is set to true, absolute paths of the bp.CgoFiles will be passed in
116// to the cgo preprocessor. This in turn will set the // line comments
117// referring to those files to use absolute paths. This is needed for
118// go/packages using the legacy go list support so it is able to find
119// the original files.
120func Run(bp *build.Package, pkgdir, tmpdir string, useabs bool) (files, displayFiles []string, err error) {
121	cgoCPPFLAGS, _, _, _ := cflags(bp, true)
122	_, cgoexeCFLAGS, _, _ := cflags(bp, false)
123
124	if len(bp.CgoPkgConfig) > 0 {
125		pcCFLAGS, err := pkgConfigFlags(bp)
126		if err != nil {
127			return nil, nil, err
128		}
129		cgoCPPFLAGS = append(cgoCPPFLAGS, pcCFLAGS...)
130	}
131
132	// Allows including _cgo_export.h from .[ch] files in the package.
133	cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir)
134
135	// _cgo_gotypes.go (displayed "C") contains the type definitions.
136	files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go"))
137	displayFiles = append(displayFiles, "C")
138	for _, fn := range bp.CgoFiles {
139		// "foo.cgo1.go" (displayed "foo.go") is the processed Go source.
140		f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_")
141		files = append(files, filepath.Join(tmpdir, f+"cgo1.go"))
142		displayFiles = append(displayFiles, fn)
143	}
144
145	var cgoflags []string
146	if bp.Goroot && bp.ImportPath == "runtime/cgo" {
147		cgoflags = append(cgoflags, "-import_runtime_cgo=false")
148	}
149	if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" {
150		cgoflags = append(cgoflags, "-import_syscall=false")
151	}
152
153	var cgoFiles []string = bp.CgoFiles
154	if useabs {
155		cgoFiles = make([]string, len(bp.CgoFiles))
156		for i := range cgoFiles {
157			cgoFiles[i] = filepath.Join(pkgdir, bp.CgoFiles[i])
158		}
159	}
160
161	args := stringList(
162		"go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--",
163		cgoCPPFLAGS, cgoexeCFLAGS, cgoFiles,
164	)
165	if false {
166		log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir)
167	}
168	cmd := exec.Command(args[0], args[1:]...)
169	cmd.Dir = pkgdir
170	cmd.Stdout = os.Stderr
171	cmd.Stderr = os.Stderr
172	if err := cmd.Run(); err != nil {
173		return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err)
174	}
175
176	return files, displayFiles, nil
177}
178
179// -- unmodified from 'go build' ---------------------------------------
180
181// Return the flags to use when invoking the C or C++ compilers, or cgo.
182func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) {
183	var defaults string
184	if def {
185		defaults = "-g -O2"
186	}
187
188	cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS)
189	cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS)
190	cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS)
191	ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS)
192	return
193}
194
195// envList returns the value of the given environment variable broken
196// into fields, using the default value when the variable is empty.
197func envList(key, def string) []string {
198	v := os.Getenv(key)
199	if v == "" {
200		v = def
201	}
202	return strings.Fields(v)
203}
204
205// stringList's arguments should be a sequence of string or []string values.
206// stringList flattens them into a single []string.
207func stringList(args ...interface{}) []string {
208	var x []string
209	for _, arg := range args {
210		switch arg := arg.(type) {
211		case []string:
212			x = append(x, arg...)
213		case string:
214			x = append(x, arg)
215		default:
216			panic("stringList: invalid argument")
217		}
218	}
219	return x
220}