github.com/posener/gitfs@v1.2.2-0.20200410105819-ea4e48d73ab9/cmd/gitfs/main.go (about)

     1  // gitfs command line tool, for generating binary conetent of the used filesystems.
     2  package main
     3  
     4  import (
     5  	"context"
     6  	"flag"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strings"
    14  	"text/template"
    15  
    16  	"github.com/pkg/errors"
    17  	"golang.org/x/tools/go/packages"
    18  
    19  	"github.com/posener/gitfs"
    20  	"github.com/posener/gitfs/fsutil"
    21  	"github.com/posener/gitfs/internal/binfs"
    22  )
    23  
    24  var (
    25  	out         = flag.String("out", "gitfs.go", "Output file")
    26  	pkg         = flag.String("pkg", "", "Package name for output file (default is the package name of current directory)")
    27  	skipTestGen = flag.Bool("skip-test-gen", false, "Skip test generation")
    28  	bootstrap   = flag.Bool("bootstrap", false, "Bootstrap mode. For package internal usage.")
    29  )
    30  
    31  // templates are used for the generated files. They
    32  // are loaded with loadTemplate function call.
    33  var templates *template.Template
    34  
    35  func main() {
    36  	// Parse flags
    37  	flag.Usage = func() {
    38  		fmt.Fprintf(flag.CommandLine.Output(), usage)
    39  		flag.PrintDefaults()
    40  	}
    41  	flag.Parse()
    42  	if len(flag.Args()) == 0 {
    43  		log.Fatal("At least one file pattern should be provided.")
    44  	}
    45  
    46  	gitfs.SetLogger(log.New(os.Stderr, "[gitfs] ", log.LstdFlags))
    47  	log.Printf("Starting binary packing...")
    48  	log.Printf("Encoding version: %d", binfs.EncodeVersion)
    49  	loadTemplates()
    50  
    51  	// Fix flags.
    52  	var err error
    53  	*out, err = getOut(*out)
    54  	if err != nil {
    55  		log.Fatalf("Invalid out flag: %s", err)
    56  	}
    57  	*pkg, err = getPkg(*pkg, *out)
    58  	if err != nil {
    59  		log.Fatalf("Invalid: pkg must be provided if output is not a Go package: %s", err)
    60  	}
    61  
    62  	calls, err := binfs.LoadCalls(flag.Args()...)
    63  	if err != nil {
    64  		log.Fatalf("Failed loading binaries: %s", err)
    65  	}
    66  	if len(calls) == 0 {
    67  		log.Fatalf("Did not found any calls for gitfs.New")
    68  	}
    69  
    70  	binaries := binfs.GenerateBinaries(calls, provider)
    71  
    72  	// Generate output
    73  	createOut(binaries)
    74  	createTest(calls)
    75  }
    76  
    77  func createOut(binaries map[string]string) {
    78  	f, err := os.Create(*out)
    79  	if err != nil {
    80  		log.Fatalf("Failed creating file %q: %s", *out, err)
    81  	}
    82  	defer f.Close()
    83  
    84  	err = generate(f, binaries)
    85  	if err != nil {
    86  		defer os.Remove(*out)
    87  		log.Fatalf("Failed generating filesystem: %s", err)
    88  	}
    89  	defer goimports(*out)
    90  }
    91  
    92  func createTest(calls binfs.Calls) {
    93  	if *skipTestGen {
    94  		log.Print("Skipping test generation")
    95  		return
    96  	}
    97  	testPath := strings.TrimSuffix(*out, ".go") + "_test.go"
    98  	f, err := os.Create(testPath)
    99  	if err != nil {
   100  		log.Fatalf("Failed creating file %q: %s", testPath, err)
   101  	}
   102  	defer f.Close()
   103  
   104  	testName := strings.Title(strings.TrimSuffix(filepath.Base(*out), ".go"))
   105  
   106  	err = generateTest(f, calls, testName)
   107  	if err != nil {
   108  		defer os.Remove(testPath)
   109  		log.Fatalf("Failed generating tests: %s", err)
   110  	}
   111  	defer goimports(testPath)
   112  }
   113  
   114  func generate(w io.Writer, binaries map[string]string) error {
   115  	return templates.ExecuteTemplate(w, "binary.go.gotmpl", struct {
   116  		Package  string
   117  		Binaries map[string]string
   118  		Version  int
   119  	}{
   120  		Package:  *pkg,
   121  		Binaries: binaries,
   122  		Version:  binfs.EncodeVersion,
   123  	})
   124  }
   125  
   126  func generateTest(w io.Writer, calls binfs.Calls, testName string) error {
   127  	return templates.ExecuteTemplate(w, "test.go.gotmpl", struct {
   128  		Package  string
   129  		Calls    binfs.Calls
   130  		TestName string
   131  	}{
   132  		Package:  *pkg,
   133  		Calls:    calls,
   134  		TestName: testName,
   135  	})
   136  }
   137  
   138  // getOut fixes out variable if it points to a directory or a file
   139  // non-existing directory.
   140  func getOut(out string) (string, error) {
   141  	if out == "" {
   142  		return "gitfs.go", nil
   143  	}
   144  	st, err := os.Stat(out)
   145  	if err != nil {
   146  		// File does not exists, make sure it is a file in current directory
   147  		// or other existing directory.
   148  		if !strings.HasSuffix(out, ".go") {
   149  			return "", errors.New("output file should be a go file")
   150  		}
   151  		dir, _ := filepath.Split(out)
   152  		if dir == "" {
   153  			// The user chose to create a local file.
   154  			return out, nil
   155  		}
   156  		// The user creates a file in directory `dir`.
   157  		st, err := os.Stat(dir)
   158  		if err != nil {
   159  			return "", errors.Errorf("output directory %q not found: %s", dir, err)
   160  		}
   161  		if !st.IsDir() {
   162  			return "", errors.Errorf("output directory %q is not a directory", dir)
   163  		}
   164  		return out, nil
   165  	}
   166  	if st.IsDir() {
   167  		// If the given output is a directory, add filename 'gitfs.go'.
   168  		out = filepath.Join(out, "gitfs.go")
   169  	}
   170  	return out, nil
   171  }
   172  
   173  // getPkg fixes the package name according to the given name in the
   174  // command line or the package of the output file.
   175  func getPkg(pkg, out string) (string, error) {
   176  	if pkg != "" {
   177  		return pkg, nil
   178  	}
   179  	outDir, _ := filepath.Split(out)
   180  	if outDir == "" {
   181  		outDir = "."
   182  	}
   183  	pkgs, err := packages.Load(nil, outDir)
   184  	if err != nil {
   185  		return "", errors.Errorf("failed loading package in %q: %s", outDir, err)
   186  	}
   187  	if len(pkgs) == 0 {
   188  		return "", errors.Errorf("could not load package in %q", outDir)
   189  	}
   190  	return pkgs[0].Name, nil
   191  }
   192  
   193  func goimports(path string) {
   194  	err := exec.Command("goimports", "-w", path).Run()
   195  	if err != nil {
   196  		log.Printf("Failed goimports on %s: %s", path, err)
   197  	}
   198  }
   199  
   200  //go:generate go run . -bootstrap -out templates.go $GOFILE
   201  func loadTemplates() {
   202  	// For bootstrapping purposes, an environment variable must be set
   203  	// such that the template themselves will be loaded from local path
   204  	// when they are generating their own template.
   205  	local := ""
   206  	if *bootstrap {
   207  		log.Println("Bootstrapping gitfs templates.")
   208  		local = "."
   209  	}
   210  	fs, err := gitfs.New(context.Background(),
   211  		"github.com/posener/gitfs/cmd/gitfs/templates", gitfs.OptLocal(local))
   212  	if err != nil {
   213  		panic(err)
   214  	}
   215  	templates = template.Must(fsutil.TmplParseGlob(fs, nil, "*"))
   216  }
   217  
   218  const usage = `gitfs packs filesystems into Go binary for github.com/posener/gitfs library.
   219  
   220  Usage:
   221  
   222  	gitfs <flags> <patterns>
   223  
   224  The command will traverses all Go files in the given patterns and
   225  looks for 'gitfs.New' calls. For each of these calls, it downloads the
   226  specified project. All the projects are then saved into a single
   227  go file (default gitfs.go).
   228  When this file is compiled to a Go binary, the projects are automatically
   229  loaded from the packed version instead of remote repository.
   230  
   231  Note:
   232  
   233  The calls for 'gitfs.New' must contain an explicit string represented
   234  project name. With the current implementation, the project can't be
   235  inferred from a variable or a constant.
   236  
   237  
   238  Example:
   239  
   240  To pack all usage of gitfs filesystems in the current project, run from
   241  the root of the project the following command:
   242  
   243  	gitfs ./...
   244  
   245  Flags:
   246  
   247  `