xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/builders/protoc.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
1*9bb1b549SSpandan Das// Copyright 2017 The Bazel Authors. All rights reserved.
2*9bb1b549SSpandan Das//
3*9bb1b549SSpandan Das// Licensed under the Apache License, Version 2.0 (the "License");
4*9bb1b549SSpandan Das// you may not use this file except in compliance with the License.
5*9bb1b549SSpandan Das// You may obtain a copy of the License at
6*9bb1b549SSpandan Das//
7*9bb1b549SSpandan Das//    http://www.apache.org/licenses/LICENSE-2.0
8*9bb1b549SSpandan Das//
9*9bb1b549SSpandan Das// Unless required by applicable law or agreed to in writing, software
10*9bb1b549SSpandan Das// distributed under the License is distributed on an "AS IS" BASIS,
11*9bb1b549SSpandan Das// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*9bb1b549SSpandan Das// See the License for the specific language governing permissions and
13*9bb1b549SSpandan Das// limitations under the License.
14*9bb1b549SSpandan Das
15*9bb1b549SSpandan Das// protoc invokes the protobuf compiler and captures the resulting .pb.go file.
16*9bb1b549SSpandan Daspackage main
17*9bb1b549SSpandan Das
18*9bb1b549SSpandan Dasimport (
19*9bb1b549SSpandan Das	"bytes"
20*9bb1b549SSpandan Das	"errors"
21*9bb1b549SSpandan Das	"flag"
22*9bb1b549SSpandan Das	"fmt"
23*9bb1b549SSpandan Das	"io/ioutil"
24*9bb1b549SSpandan Das	"log"
25*9bb1b549SSpandan Das	"os"
26*9bb1b549SSpandan Das	"os/exec"
27*9bb1b549SSpandan Das	"path/filepath"
28*9bb1b549SSpandan Das	"runtime"
29*9bb1b549SSpandan Das	"strings"
30*9bb1b549SSpandan Das)
31*9bb1b549SSpandan Das
32*9bb1b549SSpandan Dastype genFileInfo struct {
33*9bb1b549SSpandan Das	base       string       // The basename of the path
34*9bb1b549SSpandan Das	path       string       // The full path to the final file
35*9bb1b549SSpandan Das	expected   bool         // Whether the file is expected by the rules
36*9bb1b549SSpandan Das	created    bool         // Whether the file was created by protoc
37*9bb1b549SSpandan Das	from       *genFileInfo // The actual file protoc produced if not Path
38*9bb1b549SSpandan Das	unique     bool         // True if this base name is unique in expected results
39*9bb1b549SSpandan Das	ambiguious bool         // True if there were more than one possible outputs that matched this file
40*9bb1b549SSpandan Das}
41*9bb1b549SSpandan Das
42*9bb1b549SSpandan Dasfunc run(args []string) error {
43*9bb1b549SSpandan Das	// process the args
44*9bb1b549SSpandan Das	args, useParamFile, err := expandParamsFiles(args)
45*9bb1b549SSpandan Das	if err != nil {
46*9bb1b549SSpandan Das		return err
47*9bb1b549SSpandan Das	}
48*9bb1b549SSpandan Das	options := multiFlag{}
49*9bb1b549SSpandan Das	descriptors := multiFlag{}
50*9bb1b549SSpandan Das	expected := multiFlag{}
51*9bb1b549SSpandan Das	imports := multiFlag{}
52*9bb1b549SSpandan Das	flags := flag.NewFlagSet("protoc", flag.ExitOnError)
53*9bb1b549SSpandan Das	protoc := flags.String("protoc", "", "The path to the real protoc.")
54*9bb1b549SSpandan Das	outPath := flags.String("out_path", "", "The base output path to write to.")
55*9bb1b549SSpandan Das	plugin := flags.String("plugin", "", "The go plugin to use.")
56*9bb1b549SSpandan Das	importpath := flags.String("importpath", "", "The importpath for the generated sources.")
57*9bb1b549SSpandan Das	flags.Var(&options, "option", "The plugin options.")
58*9bb1b549SSpandan Das	flags.Var(&descriptors, "descriptor_set", "The descriptor set to read.")
59*9bb1b549SSpandan Das	flags.Var(&expected, "expected", "The expected output files.")
60*9bb1b549SSpandan Das	flags.Var(&imports, "import", "Map a proto file to an import path.")
61*9bb1b549SSpandan Das	if err := flags.Parse(args); err != nil {
62*9bb1b549SSpandan Das		return err
63*9bb1b549SSpandan Das	}
64*9bb1b549SSpandan Das
65*9bb1b549SSpandan Das	// Output to a temporary folder and then move the contents into place below.
66*9bb1b549SSpandan Das	// This is to work around long file paths on Windows.
67*9bb1b549SSpandan Das	tmpDir, err := ioutil.TempDir("", "go_proto")
68*9bb1b549SSpandan Das	if err != nil {
69*9bb1b549SSpandan Das		return err
70*9bb1b549SSpandan Das	}
71*9bb1b549SSpandan Das	tmpDir = abs(tmpDir)        // required to work with long paths on Windows
72*9bb1b549SSpandan Das	absOutPath := abs(*outPath) // required to work with long paths on Windows
73*9bb1b549SSpandan Das	defer os.RemoveAll(tmpDir)
74*9bb1b549SSpandan Das
75*9bb1b549SSpandan Das	pluginBase := filepath.Base(*plugin)
76*9bb1b549SSpandan Das	pluginName := strings.TrimSuffix(
77*9bb1b549SSpandan Das		strings.TrimPrefix(filepath.Base(*plugin), "protoc-gen-"), ".exe")
78*9bb1b549SSpandan Das	for _, m := range imports {
79*9bb1b549SSpandan Das		options = append(options, fmt.Sprintf("M%v", m))
80*9bb1b549SSpandan Das	}
81*9bb1b549SSpandan Das	if runtime.GOOS == "windows" {
82*9bb1b549SSpandan Das		// Turn the plugin path into raw form, since we're handing it off to a non-go binary.
83*9bb1b549SSpandan Das		// This is required to work with long paths on Windows.
84*9bb1b549SSpandan Das		*plugin = "\\\\?\\" + abs(*plugin)
85*9bb1b549SSpandan Das	}
86*9bb1b549SSpandan Das	protoc_args := []string{
87*9bb1b549SSpandan Das		fmt.Sprintf("--%v_out=%v:%v", pluginName, strings.Join(options, ","), tmpDir),
88*9bb1b549SSpandan Das		"--plugin", fmt.Sprintf("%v=%v", strings.TrimSuffix(pluginBase, ".exe"), *plugin),
89*9bb1b549SSpandan Das		"--descriptor_set_in", strings.Join(descriptors, string(os.PathListSeparator)),
90*9bb1b549SSpandan Das	}
91*9bb1b549SSpandan Das	protoc_args = append(protoc_args, flags.Args()...)
92*9bb1b549SSpandan Das
93*9bb1b549SSpandan Das	var cmd *exec.Cmd
94*9bb1b549SSpandan Das	if useParamFile {
95*9bb1b549SSpandan Das		paramFile, err := ioutil.TempFile(tmpDir, "protoc-*.params")
96*9bb1b549SSpandan Das		if err != nil {
97*9bb1b549SSpandan Das			return fmt.Errorf("error creating param file for protoc: %v", err)
98*9bb1b549SSpandan Das		}
99*9bb1b549SSpandan Das		for _, arg := range protoc_args {
100*9bb1b549SSpandan Das			_, err := fmt.Fprintln(paramFile, arg)
101*9bb1b549SSpandan Das			if err != nil {
102*9bb1b549SSpandan Das				return fmt.Errorf("error writing param file for protoc: %v", err)
103*9bb1b549SSpandan Das			}
104*9bb1b549SSpandan Das		}
105*9bb1b549SSpandan Das		cmd = exec.Command(*protoc, "@"+paramFile.Name())
106*9bb1b549SSpandan Das	} else {
107*9bb1b549SSpandan Das		cmd = exec.Command(*protoc, protoc_args...)
108*9bb1b549SSpandan Das	}
109*9bb1b549SSpandan Das
110*9bb1b549SSpandan Das	cmd.Stdout = os.Stdout
111*9bb1b549SSpandan Das	cmd.Stderr = os.Stderr
112*9bb1b549SSpandan Das	if err := cmd.Run(); err != nil {
113*9bb1b549SSpandan Das		return fmt.Errorf("error running protoc: %v", err)
114*9bb1b549SSpandan Das	}
115*9bb1b549SSpandan Das	// Build our file map, and test for existance
116*9bb1b549SSpandan Das	files := map[string]*genFileInfo{}
117*9bb1b549SSpandan Das	byBase := map[string]*genFileInfo{}
118*9bb1b549SSpandan Das	for _, path := range expected {
119*9bb1b549SSpandan Das		info := &genFileInfo{
120*9bb1b549SSpandan Das			path:     path,
121*9bb1b549SSpandan Das			base:     filepath.Base(path),
122*9bb1b549SSpandan Das			expected: true,
123*9bb1b549SSpandan Das			unique:   true,
124*9bb1b549SSpandan Das		}
125*9bb1b549SSpandan Das		files[info.path] = info
126*9bb1b549SSpandan Das		if byBase[info.base] != nil {
127*9bb1b549SSpandan Das			info.unique = false
128*9bb1b549SSpandan Das			byBase[info.base].unique = false
129*9bb1b549SSpandan Das		} else {
130*9bb1b549SSpandan Das			byBase[info.base] = info
131*9bb1b549SSpandan Das		}
132*9bb1b549SSpandan Das	}
133*9bb1b549SSpandan Das	// Walk the generated files
134*9bb1b549SSpandan Das	filepath.Walk(tmpDir, func(path string, f os.FileInfo, err error) error {
135*9bb1b549SSpandan Das		relPath, err := filepath.Rel(tmpDir, path)
136*9bb1b549SSpandan Das		if err != nil {
137*9bb1b549SSpandan Das			return err
138*9bb1b549SSpandan Das		}
139*9bb1b549SSpandan Das		if relPath == "." {
140*9bb1b549SSpandan Das			return nil
141*9bb1b549SSpandan Das		}
142*9bb1b549SSpandan Das
143*9bb1b549SSpandan Das		if f.IsDir() {
144*9bb1b549SSpandan Das			if err := os.Mkdir(filepath.Join(absOutPath, relPath), f.Mode()); !os.IsExist(err) {
145*9bb1b549SSpandan Das				return err
146*9bb1b549SSpandan Das			}
147*9bb1b549SSpandan Das			return nil
148*9bb1b549SSpandan Das		}
149*9bb1b549SSpandan Das
150*9bb1b549SSpandan Das		if !strings.HasSuffix(path, ".go") {
151*9bb1b549SSpandan Das			return nil
152*9bb1b549SSpandan Das		}
153*9bb1b549SSpandan Das
154*9bb1b549SSpandan Das		info := &genFileInfo{
155*9bb1b549SSpandan Das			path:    path,
156*9bb1b549SSpandan Das			base:    filepath.Base(path),
157*9bb1b549SSpandan Das			created: true,
158*9bb1b549SSpandan Das		}
159*9bb1b549SSpandan Das
160*9bb1b549SSpandan Das		if foundInfo, ok := files[relPath]; ok {
161*9bb1b549SSpandan Das			foundInfo.created = true
162*9bb1b549SSpandan Das			foundInfo.from = info
163*9bb1b549SSpandan Das			return nil
164*9bb1b549SSpandan Das		}
165*9bb1b549SSpandan Das		files[relPath] = info
166*9bb1b549SSpandan Das		copyTo := byBase[info.base]
167*9bb1b549SSpandan Das		switch {
168*9bb1b549SSpandan Das		case copyTo == nil:
169*9bb1b549SSpandan Das			// Unwanted output
170*9bb1b549SSpandan Das		case !copyTo.unique:
171*9bb1b549SSpandan Das			// not unique, no copy allowed
172*9bb1b549SSpandan Das		case copyTo.from != nil:
173*9bb1b549SSpandan Das			copyTo.ambiguious = true
174*9bb1b549SSpandan Das			info.ambiguious = true
175*9bb1b549SSpandan Das		default:
176*9bb1b549SSpandan Das			copyTo.from = info
177*9bb1b549SSpandan Das			copyTo.created = true
178*9bb1b549SSpandan Das			info.expected = true
179*9bb1b549SSpandan Das		}
180*9bb1b549SSpandan Das		return nil
181*9bb1b549SSpandan Das	})
182*9bb1b549SSpandan Das	buf := &bytes.Buffer{}
183*9bb1b549SSpandan Das	for _, f := range files {
184*9bb1b549SSpandan Das		switch {
185*9bb1b549SSpandan Das		case f.expected && !f.created:
186*9bb1b549SSpandan Das			// Some plugins only create output files if the proto source files have
187*9bb1b549SSpandan Das			// have relevant definitions (e.g., services for grpc_gateway). Create
188*9bb1b549SSpandan Das			// trivial files that the compiler will ignore for missing outputs.
189*9bb1b549SSpandan Das			data := []byte("// +build ignore\n\npackage ignore")
190*9bb1b549SSpandan Das			if err := ioutil.WriteFile(abs(f.path), data, 0644); err != nil {
191*9bb1b549SSpandan Das				return err
192*9bb1b549SSpandan Das			}
193*9bb1b549SSpandan Das		case f.expected && f.ambiguious:
194*9bb1b549SSpandan Das			fmt.Fprintf(buf, "Ambiguious output %v.\n", f.path)
195*9bb1b549SSpandan Das		case f.from != nil:
196*9bb1b549SSpandan Das			data, err := ioutil.ReadFile(f.from.path)
197*9bb1b549SSpandan Das			if err != nil {
198*9bb1b549SSpandan Das				return err
199*9bb1b549SSpandan Das			}
200*9bb1b549SSpandan Das			if err := ioutil.WriteFile(abs(f.path), data, 0644); err != nil {
201*9bb1b549SSpandan Das				return err
202*9bb1b549SSpandan Das			}
203*9bb1b549SSpandan Das		case !f.expected:
204*9bb1b549SSpandan Das			//fmt.Fprintf(buf, "Unexpected output %v.\n", f.path)
205*9bb1b549SSpandan Das		}
206*9bb1b549SSpandan Das		if buf.Len() > 0 {
207*9bb1b549SSpandan Das			fmt.Fprintf(buf, "Check that the go_package option is %q.", *importpath)
208*9bb1b549SSpandan Das			return errors.New(buf.String())
209*9bb1b549SSpandan Das		}
210*9bb1b549SSpandan Das	}
211*9bb1b549SSpandan Das
212*9bb1b549SSpandan Das	return nil
213*9bb1b549SSpandan Das}
214*9bb1b549SSpandan Das
215*9bb1b549SSpandan Dasfunc main() {
216*9bb1b549SSpandan Das	if err := run(os.Args[1:]); err != nil {
217*9bb1b549SSpandan Das		log.Fatal(err)
218*9bb1b549SSpandan Das	}
219*9bb1b549SSpandan Das}
220