diff --git a/go/tools/builders/BUILD.bazel b/go/tools/builders/BUILD.bazel index cf8488b759..97f533aaf0 100644 --- a/go/tools/builders/BUILD.bazel +++ b/go/tools/builders/BUILD.bazel @@ -121,6 +121,7 @@ go_tool_binary( go_tool_binary( name = "go-protoc", srcs = [ + "env.go", "flags.go", "protoc.go", ], diff --git a/go/tools/builders/compile.go b/go/tools/builders/compile.go index ff20c9ff3f..08f138501d 100644 --- a/go/tools/builders/compile.go +++ b/go/tools/builders/compile.go @@ -49,6 +49,7 @@ func run(args []string) error { if err := goenv.update(); err != nil { return err } + absOutput := abs(*output) // required to work with long paths on Windows var matcher func(f *goMetadata) bool switch *testfilter { @@ -80,7 +81,7 @@ func run(args []string) error { } } if len(files) <= 0 { - return ioutil.WriteFile(*output, []byte(""), 0644) + return ioutil.WriteFile(absOutput, []byte(""), 0644) } goargs := []string{"tool", "compile"} @@ -102,7 +103,7 @@ func run(args []string) error { goargs = append(goargs, "-importmap", mapping) strictdeps = append(strictdeps, source) } - goargs = append(goargs, "-pack", "-o", *output) + goargs = append(goargs, "-pack", "-o", absOutput) goargs = append(goargs, flags.Args()...) for _, f := range files { goargs = append(goargs, f.filename) diff --git a/go/tools/builders/env.go b/go/tools/builders/env.go index cbc23c5eea..ca272b73c9 100644 --- a/go/tools/builders/env.go +++ b/go/tools/builders/env.go @@ -41,6 +41,10 @@ type GoEnv struct { ld_flags multiFlag } +// abs returns the absolute representation of path. Some tools/APIs require +// absolute paths to work correctly. Most notably, golang on Windows cannot +// handle relative paths to files whose absolute path is > ~250 chars, while +// it can handle absolute paths. See http://goo.gl/eqeWjm. func abs(path string) string { if abs, err := filepath.Abs(path); err != nil { return path diff --git a/go/tools/builders/filter.go b/go/tools/builders/filter.go index d000987ffd..e80aa011c9 100644 --- a/go/tools/builders/filter.go +++ b/go/tools/builders/filter.go @@ -37,7 +37,7 @@ type goMetadata struct { func readFiles(bctx build.Context, inputs []string) ([]*goMetadata, error) { outputs := []*goMetadata{} for _, input := range inputs { - if m, err := readGoMetadata(bctx, input, true); err != nil { + if m, err := readGoMetadata(bctx, abs(input), true); err != nil { return nil, err } else if m.matched { outputs = append(outputs, m) diff --git a/go/tools/builders/filter_test.go b/go/tools/builders/filter_test.go index ec887c440a..e7ca86507c 100644 --- a/go/tools/builders/filter_test.go +++ b/go/tools/builders/filter_test.go @@ -124,3 +124,8 @@ func runTest(t *testing.T, bctx build.Context, inputs []string, expect []string) t.Errorf("filter %v,%v,%v,%v: expect %v got %v", bctx.GOOS, bctx.GOARCH, bctx.CgoEnabled, bctx.BuildTags, expect, got) } } + +// abs is a dummy env.go abs to avoid depending on env.go and flags.go. +func abs(p string) string { + return p +} diff --git a/go/tools/builders/protoc.go b/go/tools/builders/protoc.go index 15ddd795f8..c0e52afd46 100644 --- a/go/tools/builders/protoc.go +++ b/go/tools/builders/protoc.go @@ -57,6 +57,17 @@ func run(args []string) error { if err := flags.Parse(args); err != nil { return err } + + // Output to a temporary folder and then move the contents into place below. + // This is to work around long file paths on Windows. + tmpDir, err := ioutil.TempDir("", "go_proto") + if err != nil { + return err + } + tmpDir = abs(tmpDir) // required to work with long paths on Windows + absOutPath := abs(*outPath) // required to work with long paths on Windows + defer os.RemoveAll(tmpDir) + pluginBase := filepath.Base(*plugin) pluginName := strings.TrimSuffix( strings.TrimPrefix(filepath.Base(*plugin), "protoc-gen-"), ".exe") @@ -64,7 +75,7 @@ func run(args []string) error { options = append(options, fmt.Sprintf("M%v", m)) } protoc_args := []string{ - fmt.Sprintf("--%v_out=%v:%v", pluginName, strings.Join(options, ","), *outPath), + fmt.Sprintf("--%v_out=%v:%v", pluginName, strings.Join(options, ","), tmpDir), "--plugin", fmt.Sprintf("%v=%v", strings.TrimSuffix(pluginBase, ".exe"), *plugin), "--descriptor_set_in", strings.Join(descriptors, string(os.PathListSeparator)), } @@ -95,28 +106,45 @@ func run(args []string) error { } } // Walk the generated files - filepath.Walk(*outPath, func(path string, f os.FileInfo, err error) error { - if !strings.HasSuffix(path, ".go") { + filepath.Walk(tmpDir, func(path string, f os.FileInfo, err error) error { + relPath, err := filepath.Rel(tmpDir, path) + if err != nil { + return err + } + if relPath == "." { return nil } - info := files[path] - if info != nil { - info.created = true + + if f.IsDir() { + if err := os.Mkdir(filepath.Join(absOutPath, relPath), f.Mode()); !os.IsExist(err) { + return err + } return nil } - info = &genFileInfo{ + + if !strings.HasSuffix(path, ".go") { + return nil + } + + info := &genFileInfo{ path: path, base: filepath.Base(path), created: true, } - files[path] = info + + if foundInfo, ok := files[relPath]; ok { + foundInfo.created = true + foundInfo.from = info + return nil + } + files[relPath] = info copyTo := byBase[info.base] switch { case copyTo == nil: // Unwanted output case !copyTo.unique: // not unique, no copy allowed - case info.from != nil: + case copyTo.from != nil: copyTo.ambiguious = true info.ambiguious = true default: @@ -138,7 +166,7 @@ func run(args []string) error { if err != nil { return err } - if err := ioutil.WriteFile(f.path, data, 0644); err != nil { + if err := ioutil.WriteFile(abs(f.path), data, 0644); err != nil { return err } case !f.expected: @@ -149,6 +177,7 @@ func run(args []string) error { return errors.New(buf.String()) } } + return nil }