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  `