github.com/apache/beam/sdks/v2@v2.48.2/go/cmd/starcgen/starcgen.go (about) 1 // Licensed to the Apache Software Foundation (ASF) under one or more 2 // contributor license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright ownership. 4 // The ASF licenses this file to You under the Apache License, Version 2.0 5 // (the "License"); you may not use this file except in compliance with 6 // the License. You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 // starcgen is a tool to generate specialized type assertion shims to be 17 // used in Apache Beam Go SDK pipelines instead of the default reflection shim. 18 // This is done through static analysis of go sources for the package in question. 19 // 20 // The generated type assertion shims have much better performance than the default 21 // reflection based shims used by beam. Reflection is convenient for development, 22 // but is an unnecessary expense on pipeline performance. 23 // 24 // # Using This Tool 25 // 26 // This tool is intended for use with `go generate`. The recommended convention 27 // putting the types and functions used in a separate package from pipeline construction. 28 // Then, the tool can be used as follows: 29 // 30 // //go:generate go install github.com/apache/beam/sdks/v2/go/cmd/starcgen 31 // //go:generate starcgen --package=<mypackagename> 32 // //go:generate go fmt 33 // 34 // This will generate registrations and shim types for all types and functions 35 // in the package, in a file `<mypackagename>.shims.go`. 36 // 37 // Alternatively, it's possible to specify the specific input files and identifiers within 38 // the package for generation. 39 // 40 // //go:generate go install github.com/apache/beam/sdks/v2/go/cmd/starcgen 41 // //go:generate starcgen --package=<mypackagename> --inputs=foo.go --identifiers=myFn,myStructFn --output=custom.shims.go 42 // //go:generate go fmt 43 package main 44 45 import ( 46 "flag" 47 "fmt" 48 "go/ast" 49 "go/importer" 50 "go/parser" 51 "go/token" 52 "io" 53 "log" 54 "os" 55 "path/filepath" 56 "strings" 57 58 "github.com/apache/beam/sdks/v2/go/pkg/beam/util/starcgenx" 59 ) 60 61 var ( 62 inputs = flag.String("inputs", "", "comma separated list of file with types for which to create shims") 63 intendedPkg = flag.String("package", "", "a filter on input go files. Required if inputs unset.") 64 output = flag.String("output", "", "output file with types to create") 65 ids = flag.String("identifiers", "", "comma separated list of package local identifiers for which to generate code") 66 debug = flag.Bool("debug", false, "print out a debugging header in the shim file to help diagnose errors") 67 ) 68 69 // Generate takes the typechecked inputs, and generates the shim file for the relevant 70 // identifiers. 71 func Generate(w io.Writer, filename, pkg string, ids []string, fset *token.FileSet, files []*ast.File) error { 72 e := starcgenx.NewExtractor(pkg) 73 e.Ids = ids 74 e.Debug = *debug 75 76 // Importing from source should work in most cases. 77 imp := importer.For("source", nil) 78 if err := e.FromAsts(imp, fset, files); err != nil { 79 // Always print out the debugging info to the file. 80 if _, errw := w.Write(e.Bytes()); errw != nil { 81 return fmt.Errorf("error writing debug data to file after err %v:%v", err, errw) 82 } 83 err = fmt.Errorf("error extracting from asts: %v", err) 84 e.Printf("%v", err) 85 return err 86 } 87 data := e.Generate(filename) 88 if err := write(w, []byte(license)); err != nil { 89 return err 90 } 91 return write(w, data) 92 } 93 94 func write(w io.Writer, data []byte) error { 95 n, err := w.Write(data) 96 if err != nil && n < len(data) { 97 return fmt.Errorf("short write of data got %d, want %d", n, len(data)) 98 } 99 return err 100 } 101 102 func usage() { 103 fmt.Fprintf(os.Stderr, "Usage: %v [options] --inputs=<comma separated of go files>\n", filepath.Base(os.Args[0])) 104 flag.PrintDefaults() 105 } 106 107 func main() { 108 flag.Usage = usage 109 flag.Parse() 110 111 log.SetFlags(log.Lshortfile) 112 log.SetPrefix("starcgen: ") 113 114 dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 115 if err != nil { 116 log.Fatal(err) 117 } 118 119 var ipts []string 120 // If inputs are empty, parse all go files in the local directory. 121 if len(*inputs) == 0 { 122 if *intendedPkg == "" { 123 log.Fatal("--package flag is required to be set when --inputs unset") 124 } 125 globbed, err := filepath.Glob(filepath.Join(dir, "*.go")) 126 if err != nil { 127 log.Fatal(err) 128 } 129 ipts = globbed 130 } else { 131 ipts = strings.Split(*inputs, ",") 132 } 133 134 // Get an output file for pre-processing if necessary. 135 if *output == "" && *intendedPkg != "" { 136 *output = *intendedPkg + ".shims.go" 137 } 138 139 outputBase := filepath.Base(*output) 140 141 fset := token.NewFileSet() 142 var fs []*ast.File 143 pkg := *intendedPkg 144 for _, i := range ipts { 145 // Ignore the existing shim file when re-generating. 146 if strings.HasSuffix(i, outputBase) { 147 continue 148 } 149 150 f, err := parser.ParseFile(fset, i, nil, 0) 151 if err != nil { 152 log.Fatal(err) // parse error 153 } 154 if pkg == "" { 155 pkg = f.Name.Name 156 } else if pkg != f.Name.Name { 157 // If we've set an intended package, just filter files outside that package. 158 // eg. for pkg_tests in the same directory. 159 if *intendedPkg != "" { 160 continue 161 } 162 log.Fatalf("Input file %v has mismatched package path, got %q, want %q", i, f.Name.Name, pkg) 163 } 164 fs = append(fs, f) 165 } 166 if pkg == "" { 167 log.Fatalf("No package detected in input files: %v", inputs) 168 } 169 170 f, err := os.OpenFile(*output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 171 if err != nil { 172 log.Fatalf("error opening %q: %v", *output, err) 173 } 174 splitIds := make([]string, 0) // If no ids are specified, we should pass an empty slice. 175 if len(*ids) > 0 { 176 splitIds = strings.Split(*ids, ",") 177 } 178 if err := Generate(f, *output, pkg, splitIds, fset, fs); err != nil { 179 log.Fatal(err) 180 } 181 } 182 183 const license = `// Licensed to the Apache Software Foundation (ASF) under one or more 184 // contributor license agreements. See the NOTICE file distributed with 185 // this work for additional information regarding copyright ownership. 186 // The ASF licenses this file to You under the Apache License, Version 2.0 187 // (the "License"); you may not use this file except in compliance with 188 // the License. You may obtain a copy of the License at 189 // 190 // http://www.apache.org/licenses/LICENSE-2.0 191 // 192 // Unless required by applicable law or agreed to in writing, software 193 // distributed under the License is distributed on an "AS IS" BASIS, 194 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 195 // See the License for the specific language governing permissions and 196 // limitations under the License. 197 198 `