Commit 3c667ef4 authored by Russ Cox's avatar Russ Cox

cmd/go: split out cmd/go/internal/work

This is one CL in a long sequence of changes to break up the
go command from one package into a plausible group of packages.

This sequence is concerned only with moving code, not changing
or cleaning up code. There will still be more cleanup after this sequence.

The entire sequence will be submitted together: it is not a goal
for the tree to build at every step.

For #18653.

Change-Id: Icdd181098f9f0e81f68bf201e6867cdd8f820300
Reviewed-on: https://go-review.googlesource.com/36197Reviewed-by: 's avatarDavid Crawshaw <crawshaw@golang.org>
parent eb93b20c
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"os"
"reflect"
"testing"
)
func TestRemoveDevNull(t *testing.T) {
fi, err := os.Lstat(os.DevNull)
if err != nil {
t.Skip(err)
}
if fi.Mode().IsRegular() {
t.Errorf("Lstat(%s).Mode().IsRegular() = true; expected false", os.DevNull)
}
mayberemovefile(os.DevNull)
_, err = os.Lstat(os.DevNull)
if err != nil {
t.Errorf("mayberemovefile(%s) did remove it; oops", os.DevNull)
}
}
func TestSplitPkgConfigOutput(t *testing.T) {
for _, test := range []struct {
in []byte
want []string
}{
{[]byte(`-r:foo -L/usr/white\ space/lib -lfoo\ bar -lbar\ baz`), []string{"-r:foo", "-L/usr/white space/lib", "-lfoo bar", "-lbar baz"}},
{[]byte(`-lextra\ fun\ arg\\`), []string{`-lextra fun arg\`}},
{[]byte(`broken flag\`), []string{"broken", "flag"}},
{[]byte("\textra whitespace\r\n"), []string{"extra", "whitespace"}},
{[]byte(" \r\n "), nil},
} {
got := splitPkgConfigOutput(test.in)
if !reflect.DeepEqual(got, test.want) {
t.Errorf("splitPkgConfigOutput(%v) = %v; want %v", test.in, got, test.want)
}
}
}
......@@ -5,14 +5,16 @@
package main
import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/work"
)
var cmdClean = &base.Command{
......@@ -74,7 +76,7 @@ func init() {
// mentioned explicitly in the docs but they
// are part of the build flags.
addBuildFlags(cmdClean)
work.AddBuildFlags(cmdClean)
}
func runClean(cmd *base.Command, args []string) {
......@@ -124,8 +126,8 @@ func clean(p *load.Package) {
return
}
var b builder
b.print = fmt.Print
var b work.Builder
b.Print = fmt.Print
packageFile := map[string]bool{}
if p.Name != "main" {
......@@ -176,7 +178,7 @@ func clean(p *load.Package) {
}
if cfg.BuildN || cfg.BuildX {
b.showcmd(p.Dir, "rm -f %s", strings.Join(allRemove, " "))
b.Showcmd(p.Dir, "rm -f %s", strings.Join(allRemove, " "))
}
toRemove := map[string]bool{}
......@@ -189,7 +191,7 @@ func clean(p *load.Package) {
// TODO: Remove once Makefiles are forgotten.
if cleanDir[name] {
if cfg.BuildN || cfg.BuildX {
b.showcmd(p.Dir, "rm -r %s", name)
b.Showcmd(p.Dir, "rm -r %s", name)
if cfg.BuildN {
continue
}
......@@ -212,7 +214,7 @@ func clean(p *load.Package) {
if cleanI && p.Internal.Target != "" {
if cfg.BuildN || cfg.BuildX {
b.showcmd("", "rm -f %s", p.Internal.Target)
b.Showcmd("", "rm -f %s", p.Internal.Target)
}
if !cfg.BuildN {
removeFile(p.Internal.Target)
......
......@@ -5,13 +5,15 @@
package main
import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"fmt"
"os"
"runtime"
"strings"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/work"
)
var cmdEnv = &base.Command{
......@@ -29,8 +31,8 @@ each named variable on its own line.
}
func mkEnv() []cfg.EnvVar {
var b builder
b.init()
var b work.Builder
b.Init()
env := []cfg.EnvVar{
{"GOARCH", cfg.Goarch},
......@@ -48,10 +50,10 @@ func mkEnv() []cfg.EnvVar {
{"TERM", "dumb"},
}
if gccgoBin != "" {
env = append(env, cfg.EnvVar{"GCCGO", gccgoBin})
if work.GccgoBin != "" {
env = append(env, cfg.EnvVar{"GCCGO", work.GccgoBin})
} else {
env = append(env, cfg.EnvVar{"GCCGO", gccgoName})
env = append(env, cfg.EnvVar{"GCCGO", work.GccgoName})
}
switch cfg.Goarch {
......@@ -61,10 +63,10 @@ func mkEnv() []cfg.EnvVar {
env = append(env, cfg.EnvVar{"GO386", os.Getenv("GO386")})
}
cmd := b.gccCmd(".")
cmd := b.GccCmd(".")
env = append(env, cfg.EnvVar{"CC", cmd[0]})
env = append(env, cfg.EnvVar{"GOGCCFLAGS", strings.Join(cmd[3:], " ")})
cmd = b.gxxCmd(".")
cmd = b.GxxCmd(".")
env = append(env, cfg.EnvVar{"CXX", cmd[0]})
if cfg.BuildContext.CgoEnabled {
......@@ -87,11 +89,11 @@ func findEnv(env []cfg.EnvVar, name string) string {
// extraEnvVars returns environment variables that should not leak into child processes.
func extraEnvVars() []cfg.EnvVar {
var b builder
b.init()
cppflags, cflags, cxxflags, fflags, ldflags := b.cflags(&load.Package{})
var b work.Builder
b.Init()
cppflags, cflags, cxxflags, fflags, ldflags := b.CFlags(&load.Package{})
return []cfg.EnvVar{
{"PKG_CONFIG", b.pkgconfigCmd()},
{"PKG_CONFIG", b.PkgconfigCmd()},
{"CGO_CFLAGS", strings.Join(cflags, " ")},
{"CGO_CPPFLAGS", strings.Join(cppflags, " ")},
{"CGO_CXXFLAGS", strings.Join(cxxflags, " ")},
......
......@@ -10,6 +10,7 @@ import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/work"
"fmt"
"io"
"log"
......@@ -134,7 +135,7 @@ var (
)
func init() {
addBuildFlags(cmdGenerate)
work.AddBuildFlags(cmdGenerate)
cmdGenerate.Flag.StringVar(&generateRunFlag, "run", "", "")
}
......
......@@ -9,6 +9,7 @@ import (
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/str"
"cmd/go/internal/work"
"fmt"
"go/build"
"os"
......@@ -83,7 +84,7 @@ var getFix = cmdGet.Flag.Bool("fix", false, "")
var getInsecure = cmdGet.Flag.Bool("insecure", false, "")
func init() {
addBuildFlags(cmdGet)
work.AddBuildFlags(cmdGet)
cmdGet.Run = runGet // break init loop
}
......@@ -157,7 +158,7 @@ func runGet(cmd *base.Command, args []string) {
return
}
installPackages(args, true)
work.InstallPackages(args, true)
}
// downloadPaths prepares the list of paths to pass to download.
......
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package base
import "strings"
// envForDir returns a copy of the environment
// suitable for running in the given directory.
// The environment is the current process's environment
// but with an updated $PWD, so that an os.Getwd in the
// child will be faster.
func EnvForDir(dir string, base []string) []string {
// Internally we only use rooted paths, so dir is rooted.
// Even if dir is not rooted, no harm done.
return MergeEnvLists([]string{"PWD=" + dir}, base)
}
// MergeEnvLists merges the two environment lists such that
// variables with the same name in "in" replace those in "out".
// This always returns a newly allocated slice.
func MergeEnvLists(in, out []string) []string {
out = append([]string(nil), out...)
NextVar:
for _, inkv := range in {
k := strings.SplitAfterN(inkv, "=", 2)[0]
for i, outkv := range out {
if strings.HasPrefix(outkv, k) {
out[i] = inkv
continue NextVar
}
}
out = append(out, inkv)
}
return out
}
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package base
import (
"cmd/go/internal/str"
"flag"
)
// A StringsFlag is a command-line flag that interprets its argument
// as a space-separated list of possibly-quoted strings.
type StringsFlag []string
func (v *StringsFlag) Set(s string) error {
var err error
*v, err = str.SplitQuotedFields(s)
if *v == nil {
*v = []string{}
}
return err
}
func (v *StringsFlag) String() string {
return "<StringsFlag>"
}
// AddBuildFlagsNX adds the -n and -x build flags to the flag set.
func AddBuildFlagsNX(flags *flag.FlagSet) {
flags.BoolVar(&BuildN, "n", false, "")
flags.BoolVar(&BuildX, "x", false, "")
}
......@@ -7,6 +7,7 @@ package base
import (
"os"
"path/filepath"
"strings"
)
var Cwd, _ = os.Getwd()
......@@ -34,3 +35,10 @@ func RelPaths(paths []string) []string {
}
return out
}
// IsTestFile reports whether the source file is a set of tests and should therefore
// be excluded from coverage analysis.
func IsTestFile(file string) bool {
// We don't cover tests, only the code they test.
return strings.HasSuffix(file, "_test.go")
}
......@@ -95,3 +95,47 @@ func Contains(x []string, s string) bool {
}
return false
}
func isSpaceByte(c byte) bool {
return c == ' ' || c == '\t' || c == '\n' || c == '\r'
}
// SplitQuotedFields splits s into a list of fields,
// allowing single or double quotes around elements.
// There is no unescaping or other processing within
// quoted fields.
func SplitQuotedFields(s string) ([]string, error) {
// Split fields allowing '' or "" around elements.
// Quotes further inside the string do not count.
var f []string
for len(s) > 0 {
for len(s) > 0 && isSpaceByte(s[0]) {
s = s[1:]
}
if len(s) == 0 {
break
}
// Accepted quoted string. No unescaping inside.
if s[0] == '"' || s[0] == '\'' {
quote := s[0]
s = s[1:]
i := 0
for i < len(s) && s[i] != quote {
i++
}
if i >= len(s) {
return nil, fmt.Errorf("unterminated %c string", quote)
}
f = append(f, s[:i])
s = s[i+1:]
continue
}
i := 0
for i < len(s) && !isSpaceByte(s[i]) {
i++
}
f = append(f, s[:i])
s = s[i:]
}
return f, nil
}
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package work
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
)
func TestRemoveDevNull(t *testing.T) {
fi, err := os.Lstat(os.DevNull)
if err != nil {
t.Skip(err)
}
if fi.Mode().IsRegular() {
t.Errorf("Lstat(%s).Mode().IsRegular() = true; expected false", os.DevNull)
}
mayberemovefile(os.DevNull)
_, err = os.Lstat(os.DevNull)
if err != nil {
t.Errorf("mayberemovefile(%s) did remove it; oops", os.DevNull)
}
}
func TestSplitPkgConfigOutput(t *testing.T) {
for _, test := range []struct {
in []byte
want []string
}{
{[]byte(`-r:foo -L/usr/white\ space/lib -lfoo\ bar -lbar\ baz`), []string{"-r:foo", "-L/usr/white space/lib", "-lfoo bar", "-lbar baz"}},
{[]byte(`-lextra\ fun\ arg\\`), []string{`-lextra fun arg\`}},
{[]byte(`broken flag\`), []string{"broken", "flag"}},
{[]byte("\textra whitespace\r\n"), []string{"extra", "whitespace"}},
{[]byte(" \r\n "), nil},
} {
got := splitPkgConfigOutput(test.in)
if !reflect.DeepEqual(got, test.want) {
t.Errorf("splitPkgConfigOutput(%v) = %v; want %v", test.in, got, test.want)
}
}
}
func TestSharedLibName(t *testing.T) {
// TODO(avdva) - make these values platform-specific
prefix := "lib"
suffix := ".so"
testData := []struct {
args []string
pkgs []*load.Package
expected string
expectErr bool
rootedAt string
}{
{
args: []string{"std"},
pkgs: []*load.Package{},
expected: "std",
},
{
args: []string{"std", "cmd"},
pkgs: []*load.Package{},
expected: "std,cmd",
},
{
args: []string{},
pkgs: []*load.Package{pkgImportPath("gopkg.in/somelib")},
expected: "gopkg.in-somelib",
},
{
args: []string{"./..."},
pkgs: []*load.Package{pkgImportPath("somelib")},
expected: "somelib",
rootedAt: "somelib",
},
{
args: []string{"../somelib", "../somelib"},
pkgs: []*load.Package{pkgImportPath("somelib")},
expected: "somelib",
},
{
args: []string{"../lib1", "../lib2"},
pkgs: []*load.Package{pkgImportPath("gopkg.in/lib1"), pkgImportPath("gopkg.in/lib2")},
expected: "gopkg.in-lib1,gopkg.in-lib2",
},
{
args: []string{"./..."},
pkgs: []*load.Package{
pkgImportPath("gopkg.in/dir/lib1"),
pkgImportPath("gopkg.in/lib2"),
pkgImportPath("gopkg.in/lib3"),
},
expected: "gopkg.in",
rootedAt: "gopkg.in",
},
{
args: []string{"std", "../lib2"},
pkgs: []*load.Package{},
expectErr: true,
},
{
args: []string{"all", "./"},
pkgs: []*load.Package{},
expectErr: true,
},
{
args: []string{"cmd", "fmt"},
pkgs: []*load.Package{},
expectErr: true,
},
}
for _, data := range testData {
func() {
if data.rootedAt != "" {
tmpGopath, err := ioutil.TempDir("", "gopath")
if err != nil {
t.Fatal(err)
}
oldGopath := cfg.BuildContext.GOPATH
defer func() {
cfg.BuildContext.GOPATH = oldGopath
os.Chdir(base.Cwd)
err := os.RemoveAll(tmpGopath)
if err != nil {
t.Error(err)
}
}()
root := filepath.Join(tmpGopath, "src", data.rootedAt)
err = os.MkdirAll(root, 0755)
if err != nil {
t.Fatal(err)
}
cfg.BuildContext.GOPATH = tmpGopath
os.Chdir(root)
}
computed, err := libname(data.args, data.pkgs)
if err != nil {
if !data.expectErr {
t.Errorf("libname returned an error %q, expected a name", err.Error())
}
} else if data.expectErr {
t.Errorf("libname returned %q, expected an error", computed)
} else {
expected := prefix + data.expected + suffix
if expected != computed {
t.Errorf("libname returned %q, expected %q", computed, expected)
}
}
}()
}
}
func pkgImportPath(pkgpath string) *load.Package {
return &load.Package{
PackagePublic: load.PackagePublic{
ImportPath: pkgpath,
},
}
}
......@@ -9,6 +9,7 @@ import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/work"
"encoding/json"
"io"
"os"
......@@ -140,7 +141,7 @@ For more about specifying packages, see 'go help packages'.
func init() {
cmdList.Run = runList // break init cycle
addBuildFlags(cmdList)
work.AddBuildFlags(cmdList)
}
var listE = cmdList.Flag.Bool("e", false, "")
......@@ -149,7 +150,7 @@ var listJson = cmdList.Flag.Bool("json", false, "")
var nl = []byte{'\n'}
func runList(cmd *base.Command, args []string) {
buildModeInit()
work.BuildModeInit()
out := newTrackingWriter(os.Stdout)
defer out.w.Flush()
......
......@@ -16,11 +16,12 @@ import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/help"
"cmd/go/internal/work"
)
func init() {
base.Commands = []*base.Command{
cmdBuild,
work.CmdBuild,
cmdClean,
cmdDoc,
cmdEnv,
......@@ -29,7 +30,7 @@ func init() {
cmdFmt,
cmdGenerate,
cmdGet,
cmdInstall,
work.CmdInstall,
cmdList,
cmdRun,
cmdTest,
......
......@@ -5,13 +5,8 @@
package main
import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/str"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
......@@ -95,112 +90,3 @@ func pkgImportPath(path string) *load.Package {
},
}
}
func TestSharedLibName(t *testing.T) {
// TODO(avdva) - make these values platform-specific
prefix := "lib"
suffix := ".so"
testData := []struct {
args []string
pkgs []*load.Package
expected string
expectErr bool
rootedAt string
}{
{
args: []string{"std"},
pkgs: []*load.Package{},
expected: "std",
},
{
args: []string{"std", "cmd"},
pkgs: []*load.Package{},
expected: "std,cmd",
},
{
args: []string{},
pkgs: []*load.Package{pkgImportPath("gopkg.in/somelib")},
expected: "gopkg.in-somelib",
},
{
args: []string{"./..."},
pkgs: []*load.Package{pkgImportPath("somelib")},
expected: "somelib",
rootedAt: "somelib",
},
{
args: []string{"../somelib", "../somelib"},
pkgs: []*load.Package{pkgImportPath("somelib")},
expected: "somelib",
},
{
args: []string{"../lib1", "../lib2"},
pkgs: []*load.Package{pkgImportPath("gopkg.in/lib1"), pkgImportPath("gopkg.in/lib2")},
expected: "gopkg.in-lib1,gopkg.in-lib2",
},
{
args: []string{"./..."},
pkgs: []*load.Package{
pkgImportPath("gopkg.in/dir/lib1"),
pkgImportPath("gopkg.in/lib2"),
pkgImportPath("gopkg.in/lib3"),
},
expected: "gopkg.in",
rootedAt: "gopkg.in",
},
{
args: []string{"std", "../lib2"},
pkgs: []*load.Package{},
expectErr: true,
},
{
args: []string{"all", "./"},
pkgs: []*load.Package{},
expectErr: true,
},
{
args: []string{"cmd", "fmt"},
pkgs: []*load.Package{},
expectErr: true,
},
}
for _, data := range testData {
func() {
if data.rootedAt != "" {
tmpGopath, err := ioutil.TempDir("", "gopath")
if err != nil {
t.Fatal(err)
}
oldGopath := cfg.BuildContext.GOPATH
defer func() {
cfg.BuildContext.GOPATH = oldGopath
os.Chdir(base.Cwd)
err := os.RemoveAll(tmpGopath)
if err != nil {
t.Error(err)
}
}()
root := filepath.Join(tmpGopath, "src", data.rootedAt)
err = os.MkdirAll(root, 0755)
if err != nil {
t.Fatal(err)
}
cfg.BuildContext.GOPATH = tmpGopath
os.Chdir(root)
}
computed, err := libname(data.args, data.pkgs)
if err != nil {
if !data.expectErr {
t.Errorf("libname returned an error %q, expected a name", err.Error())
}
} else if data.expectErr {
t.Errorf("libname returned %q, expected an error", computed)
} else {
expected := prefix + data.expected + suffix
if expected != computed {
t.Errorf("libname returned %q, expected %q", computed, expected)
}
}
}()
}
}
......@@ -5,15 +5,17 @@
package main
import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/str"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/str"
"cmd/go/internal/work"
)
var execCmd []string // -exec flag, for run and test
......@@ -59,8 +61,8 @@ See also: go build.
func init() {
cmdRun.Run = runRun // break init loop
addBuildFlags(cmdRun)
cmdRun.Flag.Var((*stringsFlag)(&execCmd), "exec", "")
work.AddBuildFlags(cmdRun)
cmdRun.Flag.Var((*base.StringsFlag)(&execCmd), "exec", "")
}
func printStderr(args ...interface{}) (int, error) {
......@@ -68,11 +70,11 @@ func printStderr(args ...interface{}) (int, error) {
}
func runRun(cmd *base.Command, args []string) {
instrumentInit()
buildModeInit()
var b builder
b.init()
b.print = printStderr
work.InstrumentInit()
work.BuildModeInit()
var b work.Builder
b.Init()
b.Print = printStderr
i := 0
for i < len(args) && strings.HasSuffix(args[i], ".go") {
i++
......@@ -126,17 +128,17 @@ func runRun(cmd *base.Command, args []string) {
base.Fatalf("go run: no suitable source files%s", hint)
}
p.Internal.ExeName = src[:len(src)-len(".go")] // name temporary executable for first go file
a1 := b.action(modeBuild, modeBuild, p)
a := &action{f: (*builder).runProgram, args: cmdArgs, deps: []*action{a1}}
b.do(a)
a1 := b.Action(work.ModeBuild, work.ModeBuild, p)
a := &work.Action{Func: buildRunProgram, Args: cmdArgs, Deps: []*work.Action{a1}}
b.Do(a)
}
// runProgram is the action for running a binary that has already
// buildRunProgram is the action for running a binary that has already
// been compiled. We ignore exit status.
func (b *builder) runProgram(a *action) error {
cmdline := str.StringList(findExecCmd(), a.deps[0].target, a.args)
func buildRunProgram(b *work.Builder, a *work.Action) error {
cmdline := str.StringList(findExecCmd(), a.Deps[0].Target, a.Args)
if cfg.BuildN || cfg.BuildX {
b.showcmd("", "%s", strings.Join(cmdline, " "))
b.Showcmd("", "%s", strings.Join(cmdline, " "))
if cfg.BuildN {
return nil
}
......
This diff is collapsed.
......@@ -5,13 +5,16 @@
package main
import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"flag"
"fmt"
"os"
"strconv"
"strings"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/str"
"cmd/go/internal/work"
)
// The flag handling part of go test is large and distracting.
......@@ -66,7 +69,7 @@ var testFlagDefn = []*testFlagSpec{
// add build flags to testFlagDefn
func init() {
var cmd base.Command
addBuildFlags(&cmd)
work.AddBuildFlags(&cmd)
cmd.Flag.VisitAll(func(f *flag.Flag) {
if f.Name == "v" {
// test overrides the build -v flag
......@@ -144,7 +147,7 @@ func testFlags(args []string) (packageNames, passToTest []string) {
testO = value
testNeedBinary = true
case "exec":
execCmd, err = splitQuotedFields(value)
execCmd, err = str.SplitQuotedFields(value)
if err != nil {
base.Fatalf("invalid flag argument for -%s: %v", f.name, err)
}
......
......@@ -11,10 +11,11 @@ import (
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/str"
"cmd/go/internal/work"
)
func init() {
addBuildFlags(cmdVet)
work.AddBuildFlags(cmdVet)
}
var cmdVet = &base.Command{
......
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