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 `