Commit b5777571 authored by Russ Cox's avatar Russ Cox

go/build: add BuildTags to Context, allow !tag

This lets the client of go/build specify additional tags that
can be recognized in a // +build directive.  For example,
a build for a custom environment like App Engine might
include "appengine" in the BuildTags list, so that packages
can be written with some files saying

        // +build appengine   (build only on app engine)

or

        // +build !appengine  (build only when NOT on app engine)

App Engine here is just a hypothetical context.  I plan to use
this in the cmd/go sources to distinguish the bootstrap version
of cmd/go (which will not use networking) from the full version
using a custom tag.  It might also be useful in App Engine.

Also, delete Build and Script, which we did not end up using for
cmd/go and which never got turned on for real in goinstall.

R=r, adg
CC=golang-dev
https://golang.org/cl/5554079
parent bf0c1903
......@@ -44,7 +44,7 @@ var (
doInstall = flag.Bool("install", true, "build and install")
clean = flag.Bool("clean", false, "clean the package directory before installing")
nuke = flag.Bool("nuke", false, "clean the package directory and target before installing")
useMake = flag.Bool("make", true, "use make to build and install")
useMake = flag.Bool("make", true, "use make to build and install (obsolete, always true)")
verbose = flag.Bool("v", false, "verbose")
)
......@@ -336,35 +336,10 @@ func installPackage(pkg, parent string, tree *build.Tree, retry bool) (installEr
}
// Install this package.
if *useMake {
err := domake(dir, pkg, tree, dirInfo.IsCommand())
err = domake(dir, pkg, tree, dirInfo.IsCommand())
if err != nil {
return &BuildError{pkg, err}
}
return nil
}
script, err := build.Build(tree, pkg, dirInfo)
if err != nil {
return &BuildError{pkg, err}
}
if *nuke {
printf("%s: nuke\n", pkg)
script.Nuke()
} else if *clean {
printf("%s: clean\n", pkg)
script.Clean()
}
if *doInstall {
if script.Stale() {
printf("%s: install\n", pkg)
if err := script.Run(); err != nil {
return &BuildError{pkg, err}
}
} else {
printf("%s: up-to-date\n", pkg)
}
}
return nil
}
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build plan9 darwin/nocgo
// +build plan9 darwin,!cgo
package tls
......
......@@ -5,245 +5,7 @@
// Package build provides tools for building Go packages.
package build
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"time"
)
// Build produces a build Script for the given package.
func Build(tree *Tree, pkg string, info *DirInfo) (*Script, error) {
s := &Script{}
b := &build{
script: s,
path: filepath.Join(tree.SrcDir(), pkg),
}
b.obj = b.abs("_obj") + string(filepath.Separator)
b.goarch = runtime.GOARCH
if g := os.Getenv("GOARCH"); g != "" {
b.goarch = g
}
var err error
b.arch, err = ArchChar(b.goarch)
if err != nil {
return nil, err
}
// add import object files to list of Inputs
for _, pkg := range info.Imports {
t, p, err := FindTree(pkg)
if err != nil && err != ErrNotFound {
// FindTree should always be able to suggest an import
// path and tree. The path must be malformed
// (for example, an absolute or relative path).
return nil, errors.New("build: invalid import: " + pkg)
}
s.addInput(filepath.Join(t.PkgDir(), p+".a"))
}
// .go files to be built with gc
gofiles := b.abss(info.GoFiles...)
s.addInput(gofiles...)
var ofiles []string // object files to be linked or packed
// make build directory
b.mkdir(b.obj)
s.addIntermediate(b.obj)
// cgo
if len(info.CgoFiles) > 0 {
cgoFiles := b.abss(info.CgoFiles...)
s.addInput(cgoFiles...)
cgoCFiles := b.abss(info.CFiles...)
s.addInput(cgoCFiles...)
outGo, outObj := b.cgo(cgoFiles, cgoCFiles)
gofiles = append(gofiles, outGo...)
ofiles = append(ofiles, outObj...)
s.addIntermediate(outGo...)
s.addIntermediate(outObj...)
}
// compile
if len(gofiles) > 0 {
ofile := b.obj + "_go_." + b.arch
b.gc(ofile, gofiles...)
ofiles = append(ofiles, ofile)
s.addIntermediate(ofile)
}
// assemble
for _, sfile := range info.SFiles {
ofile := b.obj + sfile[:len(sfile)-1] + b.arch
sfile = b.abs(sfile)
s.addInput(sfile)
b.asm(ofile, sfile)
ofiles = append(ofiles, ofile)
s.addIntermediate(ofile)
}
if len(ofiles) == 0 {
return nil, errors.New("make: no object files to build")
}
// choose target file
var targ string
if info.IsCommand() {
// use the last part of the import path as binary name
_, bin := filepath.Split(pkg)
if runtime.GOOS == "windows" {
bin += ".exe"
}
targ = filepath.Join(tree.BinDir(), bin)
} else {
targ = filepath.Join(tree.PkgDir(), pkg+".a")
}
// make target directory
targDir, _ := filepath.Split(targ)
b.mkdir(targDir)
// link binary or pack object
if info.IsCommand() {
b.ld(targ, ofiles...)
} else {
b.gopack(targ, ofiles...)
}
s.Output = append(s.Output, targ)
return b.script, nil
}
// A Script describes the build process for a Go package.
// The Input, Intermediate, and Output fields are lists of absolute paths.
type Script struct {
Cmd []*Cmd
Input []string
Intermediate []string
Output []string
}
func (s *Script) addInput(file ...string) {
s.Input = append(s.Input, file...)
}
func (s *Script) addIntermediate(file ...string) {
s.Intermediate = append(s.Intermediate, file...)
}
// Run runs the Script's Cmds in order.
func (s *Script) Run() error {
for _, c := range s.Cmd {
if err := c.Run(); err != nil {
return err
}
}
return nil
}
// Stale returns true if the build's inputs are newer than its outputs.
func (s *Script) Stale() bool {
var latest time.Time
// get latest mtime of outputs
for _, file := range s.Output {
fi, err := os.Stat(file)
if err != nil {
// any error reading output files means stale
return true
}
if mtime := fi.ModTime(); mtime.After(latest) {
latest = mtime
}
}
for _, file := range s.Input {
fi, err := os.Stat(file)
if err != nil || fi.ModTime().After(latest) {
// any error reading input files means stale
// (attempt to rebuild to figure out why)
return true
}
}
return false
}
// Clean removes the Script's Intermediate files.
// It tries to remove every file and returns the first error it encounters.
func (s *Script) Clean() (err error) {
// Reverse order so that directories get removed after the files they contain.
for i := len(s.Intermediate) - 1; i >= 0; i-- {
if e := os.Remove(s.Intermediate[i]); err == nil {
err = e
}
}
return
}
// Nuke removes the Script's Intermediate and Output files.
// It tries to remove every file and returns the first error it encounters.
func (s *Script) Nuke() (err error) {
// Reverse order so that directories get removed after the files they contain.
for i := len(s.Output) - 1; i >= 0; i-- {
if e := os.Remove(s.Output[i]); err == nil {
err = e
}
}
if e := s.Clean(); err == nil {
err = e
}
return
}
// A Cmd describes an individual build command.
type Cmd struct {
Args []string // command-line
Stdout string // write standard output to this file, "" is passthrough
Dir string // working directory
Env []string // environment
Input []string // file paths (dependencies)
Output []string // file paths
}
func (c *Cmd) String() string {
return strings.Join(c.Args, " ")
}
// Run executes the Cmd.
func (c *Cmd) Run() error {
if c.Args[0] == "mkdir" {
for _, p := range c.Output {
if err := os.MkdirAll(p, 0777); err != nil {
return fmt.Errorf("command %q: %v", c, err)
}
}
return nil
}
out := new(bytes.Buffer)
cmd := exec.Command(c.Args[0], c.Args[1:]...)
cmd.Dir = c.Dir
cmd.Env = c.Env
cmd.Stdout = out
cmd.Stderr = out
if c.Stdout != "" {
f, err := os.Create(c.Stdout)
if err != nil {
return err
}
defer f.Close()
cmd.Stdout = f
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("command %q: %v\n%v", c, err, out)
}
return nil
}
import "errors"
// ArchChar returns the architecture character for the given goarch.
// For example, ArchChar("amd64") returns "6".
......@@ -258,188 +20,3 @@ func ArchChar(goarch string) (string, error) {
}
return "", errors.New("unsupported GOARCH " + goarch)
}
type build struct {
script *Script
path string
obj string
goarch string
arch string
}
func (b *build) abs(file string) string {
if filepath.IsAbs(file) {
return file
}
return filepath.Join(b.path, file)
}
func (b *build) abss(file ...string) []string {
s := make([]string, len(file))
for i, f := range file {
s[i] = b.abs(f)
}
return s
}
func (b *build) add(c Cmd) {
b.script.Cmd = append(b.script.Cmd, &c)
}
func (b *build) mkdir(name string) {
b.add(Cmd{
Args: []string{"mkdir", "-p", name},
Output: []string{name},
})
}
func (b *build) gc(ofile string, gofiles ...string) {
gc := b.arch + "g"
args := append([]string{gc, "-o", ofile}, gcImportArgs...)
args = append(args, gofiles...)
b.add(Cmd{
Args: args,
Input: gofiles,
Output: []string{ofile},
})
}
func (b *build) asm(ofile string, sfile string) {
asm := b.arch + "a"
b.add(Cmd{
Args: []string{asm, "-o", ofile, sfile},
Input: []string{sfile},
Output: []string{ofile},
})
}
func (b *build) ld(targ string, ofiles ...string) {
ld := b.arch + "l"
args := append([]string{ld, "-o", targ}, ldImportArgs...)
args = append(args, ofiles...)
b.add(Cmd{
Args: args,
Input: ofiles,
Output: []string{targ},
})
}
func (b *build) gopack(targ string, ofiles ...string) {
b.add(Cmd{
Args: append([]string{"gopack", "grc", targ}, ofiles...),
Input: ofiles,
Output: []string{targ},
})
}
func (b *build) cc(ofile string, cfiles ...string) {
cc := b.arch + "c"
dir := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)
inc := filepath.Join(runtime.GOROOT(), "pkg", dir)
args := []string{cc, "-FVw", "-I", inc, "-o", ofile}
b.add(Cmd{
Args: append(args, cfiles...),
Input: cfiles,
Output: []string{ofile},
})
}
func (b *build) gccCompile(ofile, cfile string) {
b.add(Cmd{
Args: b.gccArgs("-o", ofile, "-c", cfile),
Input: []string{cfile},
Output: []string{ofile},
})
}
func (b *build) gccLink(ofile string, ofiles ...string) {
b.add(Cmd{
Args: append(b.gccArgs("-o", ofile), ofiles...),
Input: ofiles,
Output: []string{ofile},
})
}
func (b *build) gccArgs(args ...string) []string {
// TODO(adg): HOST_CC
a := []string{"gcc", "-I", b.path, "-g", "-fPIC", "-O2"}
switch b.arch {
case "8":
a = append(a, "-m32")
case "6":
a = append(a, "-m64")
}
return append(a, args...)
}
var cgoRe = regexp.MustCompile(`[/\\:]`)
func (b *build) cgo(cgofiles, cgocfiles []string) (outGo, outObj []string) {
// cgo
// TODO(adg): CGOPKGPATH
// TODO(adg): CGO_FLAGS
gofiles := []string{b.obj + "_cgo_gotypes.go"}
cfiles := []string{b.obj + "_cgo_main.c", b.obj + "_cgo_export.c"}
for _, fn := range cgofiles {
f := b.obj + cgoRe.ReplaceAllString(fn[:len(fn)-2], "_")
gofiles = append(gofiles, f+"cgo1.go")
cfiles = append(cfiles, f+"cgo2.c")
}
defunC := b.obj + "_cgo_defun.c"
output := append([]string{defunC}, cfiles...)
output = append(output, gofiles...)
b.add(Cmd{
Args: append([]string{"cgo", "--"}, cgofiles...),
Dir: b.path,
Env: append(os.Environ(), "GOARCH="+b.goarch),
Input: cgofiles,
Output: output,
})
outGo = append(outGo, gofiles...)
b.script.addIntermediate(defunC, b.obj+"_cgo_export.h", b.obj+"_cgo_flags")
b.script.addIntermediate(cfiles...)
// cc _cgo_defun.c
defunObj := b.obj + "_cgo_defun." + b.arch
b.cc(defunObj, defunC)
outObj = append(outObj, defunObj)
// gcc
linkobj := make([]string, 0, len(cfiles))
for _, cfile := range cfiles {
ofile := cfile[:len(cfile)-1] + "o"
b.gccCompile(ofile, cfile)
linkobj = append(linkobj, ofile)
if !strings.HasSuffix(ofile, "_cgo_main.o") {
outObj = append(outObj, ofile)
} else {
b.script.addIntermediate(ofile)
}
}
for _, cfile := range cgocfiles {
ofile := b.obj + cgoRe.ReplaceAllString(cfile[:len(cfile)-1], "_") + "o"
b.gccCompile(ofile, cfile)
linkobj = append(linkobj, ofile)
outObj = append(outObj, ofile)
}
dynObj := b.obj + "_cgo_.o"
b.gccLink(dynObj, linkobj...)
b.script.addIntermediate(dynObj)
// cgo -dynimport
importC := b.obj + "_cgo_import.c"
b.add(Cmd{
Args: []string{"cgo", "-dynimport", dynObj},
Stdout: importC,
Input: []string{dynObj},
Output: []string{importC},
})
b.script.addIntermediate(importC)
// cc _cgo_import.ARCH
importObj := b.obj + "_cgo_import." + b.arch
b.cc(importObj, importC)
outObj = append(outObj, importObj)
return
}
......@@ -5,7 +5,6 @@
package build
import (
"os/exec"
"path/filepath"
"reflect"
"runtime"
......@@ -63,8 +62,6 @@ func ifCgo(x []string) []string {
return nil
}
const cmdtestOutput = "3"
func TestBuild(t *testing.T) {
for _, tt := range buildPkgs {
tree := Path[0] // Goroot
......@@ -78,39 +75,32 @@ func TestBuild(t *testing.T) {
t.Errorf("ScanDir(%#q) = %#v, want %#v\n", tt.dir, info, tt.info)
continue
}
if tt.dir == "go/build/cgotest" && len(info.CgoFiles) == 0 {
continue
}
}
s, err := Build(tree, tt.dir, info)
if err != nil {
t.Errorf("Build(%#q): %v", tt.dir, err)
continue
func TestMatch(t *testing.T) {
ctxt := DefaultContext
what := "default"
match := func(tag string) {
if !ctxt.match(tag) {
t.Errorf("%s context should match %s, does not", what, tag)
}
if err := s.Run(); err != nil {
t.Errorf("Run(%#q): %v", tt.dir, err)
continue
}
if tt.dir == "go/build/cmdtest" {
bin := s.Output[0]
b, err := exec.Command(bin).CombinedOutput()
if err != nil {
t.Errorf("exec %s: %v", bin, err)
continue
}
if string(b) != cmdtestOutput {
t.Errorf("cmdtest output: %s want: %s", b, cmdtestOutput)
nomatch := func(tag string) {
if ctxt.match(tag) {
t.Errorf("%s context should NOT match %s, does", what, tag)
}
}
// Deferred because cmdtest depends on pkgtest.
defer func(s *Script) {
if err := s.Nuke(); err != nil {
t.Errorf("nuking: %v", err)
}
}(s)
}
match(runtime.GOOS + "," + runtime.GOARCH)
match(runtime.GOOS + "," + runtime.GOARCH + ",!foo")
nomatch(runtime.GOOS + "," + runtime.GOARCH + ",foo")
what = "modified"
ctxt.BuildTags = []string{"foo"}
match(runtime.GOOS + "," + runtime.GOARCH)
match(runtime.GOOS + "," + runtime.GOARCH + ",foo")
nomatch(runtime.GOOS + "," + runtime.GOARCH + ",!foo")
match(runtime.GOOS + "," + runtime.GOARCH + ",!bar")
nomatch(runtime.GOOS + "," + runtime.GOARCH + ",bar")
}
......@@ -28,6 +28,7 @@ type Context struct {
GOARCH string // target architecture
GOOS string // target operating system
CgoEnabled bool // whether cgo can be used
BuildTags []string // additional tags to recognize in +build lines
// By default, ScanDir uses the operating system's
// file system calls to read directories and files.
......@@ -74,7 +75,7 @@ func (ctxt *Context) readFile(dir, file string) (string, []byte, error) {
// The DefaultContext is the default Context for builds.
// It uses the GOARCH and GOOS environment variables
// if set, or else the compiled code's GOARCH and GOOS.
var DefaultContext = defaultContext()
var DefaultContext Context = defaultContext()
var cgoEnabled = map[string]bool{
"darwin/386": true,
......@@ -121,7 +122,7 @@ type DirInfo struct {
Imports []string // All packages imported by GoFiles
// Source files
GoFiles []string // .go files in dir (excluding CgoFiles)
GoFiles []string // .go files in dir (excluding CgoFiles, TestGoFiles, XTestGoFiles)
HFiles []string // .h files in dir
CFiles []string // .c files in dir
SFiles []string // .s (and, when using cgo, .S files in dir)
......@@ -148,13 +149,71 @@ func ScanDir(dir string) (info *DirInfo, err error) {
return DefaultContext.ScanDir(dir)
}
// ScanDir returns a structure with details about the Go content found
// in the given directory. The file lists exclude:
// TODO(rsc): Move this comment to a more appropriate place.
// ScanDir returns a structure with details about the Go package
// found in the given directory.
//
// Most .go, .c, .h, and .s files in the directory are considered part
// of the package. The exceptions are:
//
// - files in package main (unless no other package is found)
// - files in package documentation
// - files ending in _test.go
// - .go files in package main (unless no other package is found)
// - .go files in package documentation
// - files starting with _ or .
// - files with build constraints not satisfied by the context
//
// Build Constraints
//
// A build constraint is a line comment beginning with the directive +build
// that lists the conditions under which a file should be included in the package.
// Constraints may appear in any kind of source file (not just Go), but
// they must be appear near the top of the file, preceded
// only by blank lines and other line comments.
//
// A build constraint is evaluated as the OR of space-separated options;
// each option evaluates as the AND of ots comma-separated terms;
// and each term is an alphanumeric word or, preceded by !, its negation.
// That is, the build constraint:
//
// // +build linux,386 darwin,!cgo
//
// corresponds to the boolean formula:
//
// (linux AND 386) OR (darwin AND (NOT cgo))
//
// During a particular build, the following words are satisfied:
//
// - the target operating system, as spelled by runtime.GOOS
// - the target architecture, as spelled by runtime.GOARCH
// - "cgo", if ctxt.CgoEnabled is true
// - any additional words listed in ctxt.BuildTags
//
// If a file's name, after stripping the extension and a possible _test suffix,
// matches *_GOOS, *_GOARCH, or *_GOOS_GOARCH for any known operating
// system and architecture values, then the file is considered to have an implicit
// build constraint requiring those terms.
//
// Examples
//
// To keep a file from being considered for the build:
//
// // +build ignore
//
// (any other unsatisfied word will work as well, but ``ignore'' is conventional.)
//
// To build a file only when using cgo, and only on Linux and OS X:
//
// // +build linux,cgo darwin,cgo
//
// Such a file is usually paired with another file implementing the
// default functionality for other systems, which in this case would
// carry the constraint:
//
// // +build !linux !darwin !cgo
//
// Naming a file dns_windows.go will cause it to be included only when
// building the package for Windows; similarly, math_386.s will be included
// only when building the package for 32-bit x86.
//
func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) {
dirs, err := ctxt.readDir(dir)
......@@ -389,7 +448,7 @@ func (ctxt *Context) shouldBuild(content []byte) bool {
if f[0] == "+build" {
ok := false
for _, tok := range f[1:] {
if ctxt.matchOSArch(tok) {
if ctxt.match(tok) {
ok = true
break
}
......@@ -441,7 +500,7 @@ func (ctxt *Context) saveCgo(filename string, di *DirInfo, cg *ast.CommentGroup)
if len(cond) > 0 {
ok := false
for _, c := range cond {
if ctxt.matchOSArch(c) {
if ctxt.match(c) {
ok = true
break
}
......@@ -550,26 +609,55 @@ func splitQuoted(s string) (r []string, err error) {
return args, err
}
// matchOSArch returns true if the name is one of:
// match returns true if the name is one of:
//
// $GOOS
// $GOARCH
// cgo (if cgo is enabled)
// nocgo (if cgo is disabled)
// !cgo (if cgo is disabled)
// tag (if tag is listed in ctxt.BuildTags)
// !tag (if tag is not listed in ctxt.BuildTags)
// a slash-separated list of any of these
//
func (ctxt *Context) matchOSArch(name string) bool {
func (ctxt *Context) match(name string) bool {
if name == "" {
return false
}
if i := strings.Index(name, ","); i >= 0 {
// comma-separated list
return ctxt.match(name[:i]) && ctxt.match(name[i+1:])
}
if strings.HasPrefix(name, "!!") { // bad syntax, reject always
return false
}
if strings.HasPrefix(name, "!") { // negation
return !ctxt.match(name[1:])
}
// Tags must be letters, digits, underscores.
// Unlike in Go identifiers, all digits is fine (e.g., "386").
for _, c := range name {
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' {
return false
}
}
// special tags
if ctxt.CgoEnabled && name == "cgo" {
return true
}
if !ctxt.CgoEnabled && name == "nocgo" {
if name == ctxt.GOOS || name == ctxt.GOARCH {
return true
}
if name == ctxt.GOOS || name == ctxt.GOARCH {
// other tags
for _, tag := range ctxt.BuildTags {
if tag == name {
return true
}
i := strings.Index(name, "/")
return i >= 0 && ctxt.matchOSArch(name[:i]) && ctxt.matchOSArch(name[i+1:])
}
return false
}
// goodOSArchFile returns false if the name contains a $GOOS or $GOARCH
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build nocgo
// +build !cgo
// Stub cgo routines for systems that do not use cgo to do network lookups.
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build nocgo windows
// +build !cgo windows
package user
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment