aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan D. Cabrera <adc@toolazydogs.com>2021-03-22 18:44:08 +0000
committerIan Lance Taylor <iant@golang.org>2021-03-22 22:48:13 +0000
commita4b4db4cdeefb7b4ea5adb09073dd123846b3588 (patch)
tree90711f0f0993b3c4be12966548bd1cfef90b2afb
parent8f19f8a9e13a2b065b49acfc72e52a2f6262f221 (diff)
downloadgo-a4b4db4cdeefb7b4ea5adb09073dd123846b3588.tar.gz
go-a4b4db4cdeefb7b4ea5adb09073dd123846b3588.zip
cmd/go2go: add ability to specify build tags in go2go
You can specify build tags, so that imports properly work during translation, by using the `-tags` option, e.g. `go tool go2go run -tags=appengine x.go2`. The `-tags` option is available for all the `go2go` sub-commands. Change-Id: Ib60e7542b10c6a561b61db23d35592b2bc7f63cd GitHub-Last-Rev: 954cccfae819aaac1cb78b97905e2a97dfdcefa7 GitHub-Pull-Request: golang/go#45147 Reviewed-on: https://go-review.googlesource.com/c/go/+/303275 Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Trust: Emmanuel Odeke <emmanuel@orijtech.com>
-rw-r--r--src/cmd/go2go/doc.go9
-rw-r--r--src/cmd/go2go/go2go_test.go63
-rw-r--r--src/cmd/go2go/main.go53
-rw-r--r--src/go/go2go/importer.go161
4 files changed, 277 insertions, 9 deletions
diff --git a/src/cmd/go2go/doc.go b/src/cmd/go2go/doc.go
index c78fd9d6a4..059b91b0bb 100644
--- a/src/cmd/go2go/doc.go
+++ b/src/cmd/go2go/doc.go
@@ -7,7 +7,7 @@
//
// Usage:
//
-// go2go [options] <command> [arguments]
+// go2go [options] <command> [go2go flags] [arguments]
//
// Commands:
//
@@ -25,6 +25,13 @@
// imports will be looked up in the usual way. If an import includes
// .go2 files, they will be translated into .go files.
//
+// The go2go flags are shared by the build, run, test, and translate commands:
+//
+// -tags tag,list
+// a comma-separated list of build tags to consider satisfied during the
+// build. For more information about build tags, see the description of
+// build constraints in the documentation for the go/build package.
+//
// There is a sample GO2PATH in cmd/go2go/testdata/go2path. It provides
// several packages that serve as examples of using generics, and may
// be useful in experimenting with your own generic code.
diff --git a/src/cmd/go2go/go2go_test.go b/src/cmd/go2go/go2go_test.go
index 296fa3edb7..4d78c271ba 100644
--- a/src/cmd/go2go/go2go_test.go
+++ b/src/cmd/go2go/go2go_test.go
@@ -321,3 +321,66 @@ func TestTransitiveGo1(t *testing.T) {
t.Fatalf(`error running "go2go build": %v`, err)
}
}
+
+func TestBuildWithTags(t *testing.T) {
+ t.Parallel()
+ buildGo2go(t)
+
+ gopath := t.TempDir()
+ testFiles{
+ {
+ "a/a.go2",
+ `package a; func ident[T any](v T) T { return v }; func F1(s string) string { return ident(s) }`,
+ },
+ {
+ "b/b_appengine.go",
+ `// +build appengine
+
+package b; import "a"; func F2(s string) string { return a.F1(s) + " App Engine!" }`,
+ },
+ {
+ "b/b.go",
+ `// +build !appengine
+
+package b; import "a"; func F2(s string) string { return a.F1(s) + " World!" }`,
+ },
+ {
+ "c/c.go2",
+ `package main; import ("fmt"; "b"); func main() { fmt.Println(b.F2("Hello")) }`,
+ },
+ }.create(t, gopath)
+
+ t.Log("go2go build")
+ cmd := exec.Command(testGo2go, "build", "-tags=appengine", "c")
+ cmd.Dir = gopath
+ cmd.Env = append(os.Environ(),
+ "GO2PATH="+gopath,
+ )
+ out, err := cmd.CombinedOutput()
+ if len(out) > 0 {
+ t.Logf("%s", out)
+ }
+ if err != nil {
+ t.Fatalf(`error running "go2go build": %v`, err)
+ }
+
+ cmdName := "./c"
+ if runtime.GOOS == "windows" {
+ cmdName += ".exe"
+ }
+ cmd = exec.Command(cmdName)
+ cmd.Dir = gopath
+ out, err = cmd.CombinedOutput()
+ t.Log("./c")
+ if len(out) > 0 {
+ t.Logf("%s", out)
+ }
+ if err != nil {
+ t.Fatalf("error running c: %v", err)
+ }
+ got := strings.Split(strings.TrimSpace(string(out)), "\n")[0]
+ want := "Hello App Engine!"
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("c output %v, want %v", got, want)
+ }
+}
diff --git a/src/cmd/go2go/main.go b/src/cmd/go2go/main.go
index a3543613ab..d778b4502f 100644
--- a/src/cmd/go2go/main.go
+++ b/src/cmd/go2go/main.go
@@ -26,6 +26,26 @@ var cmds = map[string]bool{
"translate": true,
}
+// tagsFlag is the implementation of the -tags flag.
+type tagsFlag []string
+
+var buildTags tagsFlag
+
+func (v *tagsFlag) Set(s string) error {
+ // Split on commas, ignore empty strings.
+ *v = []string{}
+ for _, s := range strings.Split(s, ",") {
+ if s != "" {
+ *v = append(*v, s)
+ }
+ }
+ return nil
+}
+
+func (v *tagsFlag) String() string {
+ return strings.Join(*v, ",")
+}
+
func main() {
flag.Usage = usage
flag.Parse()
@@ -38,6 +58,13 @@ func main() {
if !cmds[args[0]] {
usage()
}
+ cmd := args[0]
+
+ fs := flag.NewFlagSet("", flag.ContinueOnError)
+ fs.Var((*tagsFlag)(&buildTags), "tags", "tag,list")
+ fs.Parse(args[1:])
+
+ args = fs.Args()
importerTmpdir, err := ioutil.TempDir("", "go2go")
if err != nil {
@@ -47,30 +74,42 @@ func main() {
importer := go2go.NewImporter(importerTmpdir)
+ if len(buildTags) > 0 {
+ importer.SetTags(buildTags)
+ }
+
var rundir string
- if args[0] == "run" {
- tmpdir := copyToTmpdir(args[1:])
+ if cmd == "run" {
+ tmpdir := copyToTmpdir(args)
defer os.RemoveAll(tmpdir)
translate(importer, tmpdir)
nargs := []string{"run"}
- for _, arg := range args[1:] {
+ for _, arg := range args {
base := filepath.Base(arg)
f := strings.TrimSuffix(base, ".go2") + ".go"
nargs = append(nargs, f)
}
args = nargs
rundir = tmpdir
- } else if args[0] == "translate" && isGo2Files(args[1:]...) {
- for _, arg := range args[1:] {
+ } else if cmd == "translate" && isGo2Files(args...) {
+ for _, arg := range args {
translateFile(importer, arg)
}
} else {
- for _, dir := range expandPackages(args[1:]) {
+ for _, dir := range expandPackages(args) {
translate(importer, dir)
}
}
- if args[0] != "translate" {
+ if cmd != "translate" {
+ if len(buildTags) > 0 {
+ args = append([]string{
+ fmt.Sprintf("-tags=%s", strings.Join(buildTags, ",")),
+ }, args...)
+ }
+
+ args = append([]string{cmd}, args...)
+
cmd := exec.Command(gotool, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
diff --git a/src/go/go2go/importer.go b/src/go/go2go/importer.go
index 6bd2e8b602..c73dc7ba82 100644
--- a/src/go/go2go/importer.go
+++ b/src/go/go2go/importer.go
@@ -5,6 +5,7 @@
package go2go
import (
+ "bytes"
"fmt"
"go/ast"
"go/build"
@@ -17,10 +18,12 @@ import (
"log"
"os"
"os/exec"
+ "path"
"path/filepath"
"runtime"
"sort"
"strings"
+ "unicode"
)
// Importer implements the types.ImporterFrom interface.
@@ -58,6 +61,9 @@ type Importer struct {
// since it doesn't deal with import information,
// but Importer is a useful common location to store the data.
instantiations map[*types.Package]*instantiations
+
+ // build tags
+ tags map[string]bool
}
var _ types.ImporterFrom = &Importer{}
@@ -81,6 +87,13 @@ func NewImporter(tmpdir string) *Importer {
idToFunc: make(map[types.Object]*ast.FuncDecl),
idToTypeSpec: make(map[types.Object]*ast.TypeSpec),
instantiations: make(map[*types.Package]*instantiations),
+ tags: make(map[string]bool),
+ }
+}
+
+func (imp *Importer) SetTags(tags []string) {
+ for _, tag := range tags {
+ imp.tags[tag] = true
}
}
@@ -217,7 +230,11 @@ func (imp *Importer) importGo1Package(importPath, dir string, mode types.ImportM
fset := token.NewFileSet()
filter := func(fi os.FileInfo) bool {
- return !strings.HasSuffix(fi.Name(), "_test.go")
+ name := fi.Name()
+ if strings.HasSuffix(name, "_test.go") {
+ return false
+ }
+ return imp.shouldInclude(path.Join(pdir, name))
}
pkgs, err := parser.ParseDir(fset, pdir, filter, 0)
if err != nil {
@@ -412,3 +429,145 @@ func (imp *Importer) gatherTransitiveImports(path string, m map[string]bool) []s
sort.Strings(r)
return r
}
+
+var slashslash = []byte("//")
+
+// shouldInclude reports whether it is okay to use this file,
+// The rule is that in the file's leading run of // comments
+// and blank lines, which must be followed by a blank line
+// (to avoid including a Go package clause doc comment),
+// lines beginning with '// +build' are taken as build directives.
+//
+// The file is accepted only if each such line lists something
+// matching the file. For example:
+//
+// // +build windows linux
+//
+// marks the file as applicable only on Windows and Linux.
+//
+// If tags["*"] is true, then ShouldBuild will consider every
+// build tag except "ignore" to be both true and false for
+// the purpose of satisfying build tags, in order to estimate
+// (conservatively) whether a file could ever possibly be used
+// in any build.
+//
+// This code was copied from the go command internals.
+func (imp *Importer) shouldInclude(path string) bool {
+ content, err := os.ReadFile(path)
+ if err != nil {
+ return false
+ }
+
+ // Pass 1. Identify leading run of // comments and blank lines,
+ // which must be followed by a blank line.
+ end := 0
+ p := content
+ for len(p) > 0 {
+ line := p
+ if i := bytes.IndexByte(line, '\n'); i >= 0 {
+ line, p = line[:i], p[i+1:]
+ } else {
+ p = p[len(p):]
+ }
+
+ line = bytes.TrimSpace(line)
+ if len(line) == 0 { // Blank line
+ end = len(content) - len(p)
+ continue
+ }
+ if !bytes.HasPrefix(line, slashslash) { // Not comment line
+ break
+ }
+ }
+ content = content[:end]
+
+ // Pass 2. Process each line in the run.
+ p = content
+ allok := true
+ for len(p) > 0 {
+ line := p
+ if i := bytes.IndexByte(line, '\n'); i >= 0 {
+ line, p = line[:i], p[i+1:]
+ } else {
+ p = p[len(p):]
+ }
+ line = bytes.TrimSpace(line)
+ if !bytes.HasPrefix(line, slashslash) {
+ continue
+ }
+ line = bytes.TrimSpace(line[len(slashslash):])
+ if len(line) > 0 && line[0] == '+' {
+ // Looks like a comment +line.
+ f := strings.Fields(string(line))
+ if f[0] == "+build" {
+ ok := false
+ for _, tok := range f[1:] {
+ if matchTags(tok, imp.tags) {
+ ok = true
+ }
+ }
+ if !ok {
+ allok = false
+ }
+ }
+ }
+ }
+
+ return allok
+}
+
+// matchTags reports whether the name is one of:
+//
+// tag (if tags[tag] is true)
+// !tag (if tags[tag] is false)
+// a comma-separated list of any of these
+//
+func matchTags(name string, tags map[string]bool) bool {
+ if name == "" {
+ return false
+ }
+ if i := strings.Index(name, ","); i >= 0 {
+ // comma-separated list
+ ok1 := matchTags(name[:i], tags)
+ ok2 := matchTags(name[i+1:], tags)
+ return ok1 && ok2
+ }
+ if strings.HasPrefix(name, "!!") { // bad syntax, reject always
+ return false
+ }
+ if strings.HasPrefix(name, "!") { // negation
+ return len(name) > 1 && matchTag(name[1:], tags, false)
+ }
+ return matchTag(name, tags, true)
+}
+
+// matchTag reports whether the tag name is valid and satisfied by tags[name]==want.
+func matchTag(name string, tags map[string]bool, want bool) bool {
+ // Tags must be letters, digits, underscores or dots.
+ // Unlike in Go identifiers, all digits are fine (e.g., "386").
+ for _, c := range name {
+ if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
+ return false
+ }
+ }
+
+ if tags["*"] && name != "" && name != "ignore" {
+ // Special case for gathering all possible imports:
+ // if we put * in the tags map then all tags
+ // except "ignore" are considered both present and not
+ // (so we return true no matter how 'want' is set).
+ return true
+ }
+
+ have := tags[name]
+ if name == "linux" {
+ have = have || tags["android"]
+ }
+ if name == "solaris" {
+ have = have || tags["illumos"]
+ }
+ if name == "darwin" {
+ have = have || tags["ios"]
+ }
+ return have == want
+}