github.com/jasonish/buffalo@v0.8.2-0.20170413145823-bacbdd415f1b/buffalo/cmd/build.go (about) 1 package cmd 2 3 import ( 4 "archive/zip" 5 "bytes" 6 "context" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "runtime" 14 "strings" 15 "time" 16 17 "github.com/gobuffalo/buffalo/generators/assets/webpack" 18 pack "github.com/gobuffalo/packr/builder" 19 "github.com/gobuffalo/plush" 20 "github.com/spf13/cobra" 21 ) 22 23 var outputBinName string 24 var zipBin bool 25 var extractAssets bool 26 var hasDB bool 27 28 type builder struct { 29 cleanup []string 30 originalMain []byte 31 originalApp []byte 32 workDir string 33 } 34 35 func (b *builder) clean(name ...string) string { 36 path := filepath.Join(name...) 37 b.cleanup = append(b.cleanup, path) 38 return path 39 } 40 41 func (b *builder) exec(name string, args ...string) error { 42 cmd := exec.Command(name, args...) 43 fmt.Printf("--> running %s\n", strings.Join(cmd.Args, " ")) 44 cmd.Stdin = os.Stdin 45 cmd.Stderr = os.Stderr 46 cmd.Stdout = os.Stdout 47 return cmd.Run() 48 } 49 50 func (b *builder) execQuiet(name string, args ...string) error { 51 cmd := exec.Command(name, args...) 52 return cmd.Run() 53 } 54 55 func (b *builder) buildWebpack() error { 56 _, err := os.Stat("webpack.config.js") 57 if err == nil { 58 // build webpack 59 return b.exec(webpack.BinPath, "-p") 60 } 61 return nil 62 } 63 64 func (b *builder) buildAPack() error { 65 err := os.MkdirAll(b.clean("a"), 0766) 66 if err != nil { 67 return err 68 } 69 err = b.buildAInit() 70 if err != nil { 71 return err 72 } 73 err = b.buildDatabase() 74 if err != nil { 75 return err 76 } 77 return nil 78 } 79 80 func (b *builder) buildAInit() error { 81 a, err := os.Create(b.clean("a", "a.go")) 82 if err != nil { 83 return err 84 } 85 a.WriteString(aGo) 86 return nil 87 } 88 89 func (b *builder) buildDatabase() error { 90 bb := &bytes.Buffer{} 91 dgo, err := os.Create(b.clean("a", "database.go")) 92 if err != nil { 93 return err 94 } 95 if hasDB { 96 // copy the database.yml file to the migrations folder so it's available through packr 97 os.MkdirAll("./migrations", 0755) 98 d, err := os.Open("database.yml") 99 if err != nil { 100 return err 101 } 102 _, err = io.Copy(bb, d) 103 if err != nil { 104 return err 105 } 106 } 107 dgo.WriteString("package a\n") 108 dgo.WriteString(fmt.Sprintf("var DB_CONFIG = `%s`", bb.String())) 109 return nil 110 } 111 112 func (b *builder) buildPackrEmbedded() error { 113 defer os.Chdir(b.workDir) 114 p := pack.New(context.Background(), b.workDir) 115 return p.Run() 116 } 117 118 func (b *builder) disableAssetsHandling() error { 119 defer os.Chdir(b.workDir) 120 fmt.Printf("--> disable self assets handling\n") 121 122 newApp := strings.Replace(string(b.originalApp), "app.ServeFiles(\"/assets\", assetsPath())", "//app.ServeFiles(\"/assets\", assetsPath())", 1) 123 124 appgo, err := os.Create("actions/app.go") 125 if err != nil { 126 return err 127 } 128 _, err = appgo.WriteString(newApp) 129 if err != nil { 130 return err 131 } 132 133 return nil 134 } 135 136 func (b *builder) buildAssetsArchive() error { 137 defer os.Chdir(b.workDir) 138 fmt.Printf("--> build assets archive\n") 139 140 outputDir := filepath.Dir(outputBinName) 141 assetsName := filepath.Base(outputBinName) 142 target := outputDir + "/" + assetsName + "-assets.zip" 143 source := filepath.Join(b.workDir, "public", "assets") 144 145 zipfile, err := os.Create(target) 146 if err != nil { 147 return err 148 } 149 defer zipfile.Close() 150 151 archive := zip.NewWriter(zipfile) 152 defer archive.Close() 153 154 info, err := os.Stat(source) 155 if err != nil { 156 return err 157 } 158 159 var baseDir string 160 if info.IsDir() { 161 baseDir = filepath.Base(source) 162 } 163 164 filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 165 if err != nil { 166 return err 167 } 168 169 header, err := zip.FileInfoHeader(info) 170 if err != nil { 171 return err 172 } 173 174 if baseDir != "" { 175 header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source)) 176 } 177 178 if info.IsDir() { 179 header.Name += "/" 180 } else { 181 header.Method = zip.Deflate 182 } 183 184 writer, err := archive.CreateHeader(header) 185 if err != nil { 186 return err 187 } 188 189 if info.IsDir() { 190 return nil 191 } 192 193 file, err := os.Open(path) 194 if err != nil { 195 return err 196 } 197 defer file.Close() 198 _, err = io.Copy(writer, file) 199 return err 200 }) 201 202 return err 203 } 204 205 func (b *builder) buildMain() error { 206 newMain := strings.Replace(string(b.originalMain), "func main()", "func originalMain()", 1) 207 maingo, err := os.Create("main.go") 208 if err != nil { 209 return err 210 } 211 _, err = maingo.WriteString(newMain) 212 if err != nil { 213 return err 214 } 215 216 ctx := plush.NewContext() 217 ctx.Set("root", rootPath) 218 ctx.Set("hasDB", hasDB) 219 if hasDB { 220 ctx.Set("modelsPack", packagePath(rootPath)+"/models") 221 } 222 _, err = os.Stat(filepath.Join(rootPath, "grifts")) 223 if err == nil { 224 ctx.Set("griftsPack", packagePath(rootPath)+"/grifts") 225 } 226 ctx.Set("aPack", packagePath(rootPath)+"/a") 227 ctx.Set("name", filepath.Base(rootPath)) 228 s, err := plush.Render(buildMainTmpl, ctx) 229 if err != nil { 230 return err 231 } 232 f, err := os.Create(b.clean("buffalo_build_main.go")) 233 if err != nil { 234 return err 235 } 236 f.WriteString(s) 237 238 return nil 239 } 240 241 func (b *builder) cleanupBuild() { 242 fmt.Println("--> cleaning up build") 243 for _, b := range b.cleanup { 244 fmt.Printf("----> cleaning up %s\n", b) 245 os.RemoveAll(b) 246 } 247 248 pack.Clean(b.workDir) 249 250 maingo, _ := os.Create("main.go") 251 maingo.Write(b.originalMain) 252 253 appgo, _ := os.Create("actions/app.go") 254 appgo.Write(b.originalApp) 255 } 256 257 func (b *builder) cleanupTarget() { 258 fmt.Println("--> cleaning up target dir") 259 260 // Create output directory if not exists 261 outputDir := filepath.Dir(outputBinName) 262 263 if _, err := os.Stat(outputDir); os.IsNotExist(err) { 264 os.MkdirAll(outputDir, 0776) 265 fmt.Printf("----> creating target dir %s\n", outputDir) 266 } 267 268 files, _ := ioutil.ReadDir(outputDir) 269 for _, f := range files { 270 fmt.Printf("----> cleaning up %s\n", f.Name()) 271 os.RemoveAll(outputDir + f.Name()) 272 } 273 } 274 275 func (b *builder) run() error { 276 _, err := os.Stat("database.yml") 277 if err == nil { 278 hasDB = true 279 } 280 281 err = b.buildMain() 282 if err != nil { 283 return err 284 } 285 286 err = b.buildWebpack() 287 if err != nil { 288 return err 289 } 290 291 err = b.buildAPack() 292 if err != nil { 293 return err 294 } 295 296 err = b.buildMain() 297 if err != nil { 298 return err 299 } 300 301 if extractAssets { 302 err = b.buildAssetsArchive() 303 if err != nil { 304 return err 305 } 306 err = b.disableAssetsHandling() 307 if err != nil { 308 return err 309 } 310 return b.buildBin() 311 } 312 313 // if zipBin { 314 // err = b.buildBin() 315 // if err != nil { 316 // return err 317 // } 318 // return b.buildpackrZip() 319 // } 320 321 err = b.buildPackrEmbedded() 322 if err != nil { 323 return err 324 } 325 return b.buildBin() 326 } 327 328 func (b *builder) buildBin() error { 329 buildArgs := []string{"build", "-v", "-o", outputBinName} 330 _, err := exec.LookPath("git") 331 buildTime := fmt.Sprintf("\"%s\"", time.Now().Format(time.RFC3339)) 332 version := buildTime 333 if err == nil { 334 cmd := exec.Command("git", "rev-parse", "--short", "HEAD") 335 out := &bytes.Buffer{} 336 cmd.Stdout = out 337 err = cmd.Run() 338 if err == nil && out.String() != "" { 339 version = strings.TrimSpace(out.String()) 340 } 341 } 342 buildArgs = append(buildArgs, "-ldflags", fmt.Sprintf("-X main.version=%s -X main.buildTime=%s", version, buildTime)) 343 344 return b.exec("go", buildArgs...) 345 } 346 347 // buildCmd represents the build command 348 var buildCmd = &cobra.Command{ 349 Use: "build", 350 Aliases: []string{"b", "bill"}, 351 Short: "Builds a Buffalo binary, including bundling of assets (packr & webpack)", 352 RunE: func(cc *cobra.Command, args []string) error { 353 originalMain := &bytes.Buffer{} 354 maingo, err := os.Open("main.go") 355 _, err = originalMain.ReadFrom(maingo) 356 if err != nil { 357 return err 358 } 359 maingo.Close() 360 361 originalApp := &bytes.Buffer{} 362 appgo, err := os.Open("actions/app.go") 363 _, err = originalApp.ReadFrom(appgo) 364 if err != nil { 365 return err 366 } 367 appgo.Close() 368 369 pwd, _ := os.Getwd() 370 b := builder{ 371 cleanup: []string{}, 372 originalMain: originalMain.Bytes(), 373 originalApp: originalApp.Bytes(), 374 workDir: pwd, 375 } 376 defer b.cleanupBuild() 377 378 b.cleanupTarget() 379 return b.run() 380 }, 381 } 382 383 func init() { 384 RootCmd.AddCommand(buildCmd) 385 pwd, _ := os.Getwd() 386 output := filepath.Join("bin", filepath.Base(pwd)) 387 388 if runtime.GOOS == "windows" { 389 output += ".exe" 390 } 391 392 buildCmd.Flags().StringVarP(&outputBinName, "output", "o", output, "set the name of the binary") 393 buildCmd.Flags().BoolVarP(&zipBin, "zip", "z", false, "zips the assets to the binary, this requires zip installed") 394 buildCmd.Flags().BoolVarP(&extractAssets, "extract-assets", "e", false, "extract the assets and put them in a distinct archive") 395 } 396 397 var buildMainTmpl = `package main 398 399 import ( 400 "fmt" 401 "log" 402 "os" 403 404 "github.com/markbates/grift/grift" 405 "github.com/gobuffalo/packr" 406 _ "<%= aPack %>" 407 <%= if (modelsPack) { %> 408 "io" 409 "io/ioutil" 410 "path/filepath" 411 "<%= modelsPack %>" 412 <% } %> 413 <%= if (griftsPack) { %> 414 _ "<%= griftsPack %>" 415 <% } %> 416 ) 417 418 var version = "unknown" 419 var buildTime = "unknown" 420 var migrationBox packr.Box 421 422 func main() { 423 args := os.Args 424 if len(args) == 1 { 425 originalMain() 426 } 427 c := args[1] 428 switch c { 429 <%= if (modelsPack) { %> 430 case "migrate": 431 migrate() 432 <% } %> 433 case "start", "run", "serve": 434 printVersion() 435 originalMain() 436 case "version": 437 printVersion() 438 case "task", "t", "tasks": 439 err := grift.Run(args[2], grift.NewContext(args[2])) 440 if err != nil { 441 log.Fatal(err) 442 } 443 default: 444 log.Fatalf("Could not find a command named: %s", c) 445 } 446 } 447 448 func printVersion() { 449 fmt.Printf("<%= name %> version %s (%s)\n\n", version, buildTime) 450 } 451 452 <%= if (modelsPack) { %> 453 func migrate() { 454 var err error 455 migrationBox = packr.NewBox("./migrations") 456 fmt.Println("--> Running migrations") 457 path, err := unpackMigrations() 458 if err != nil { 459 log.Fatalf("Failed to unpack migrations: %s", err) 460 } 461 defer os.RemoveAll(path) 462 463 models.DB.MigrateUp(path) 464 } 465 466 func unpackMigrations() (string, error) { 467 dir, err := ioutil.TempDir("", "<%= name %>-migrations") 468 if err != nil { 469 log.Fatalf("Unable to create temp directory: %s", err) 470 } 471 472 migrationBox.Walk(func(path string, f packr.File) error { 473 file, err := os.Create(filepath.Join(dir, path)) 474 if err != nil { 475 log.Fatalf("Failed to write migration to disk: %s", err) 476 } 477 _, err = io.Copy(file, f) 478 if err != nil { 479 log.Fatalf("Failed to write migration to disk: %s", err) 480 } 481 return nil 482 }) 483 484 return dir, nil 485 } 486 <% } %> 487 ` 488 489 var aGo = `package a 490 491 import ( 492 "log" 493 "os" 494 ) 495 496 func init() { 497 dropDatabaseYml() 498 } 499 500 func dropDatabaseYml() { 501 if DB_CONFIG != "" { 502 _, err := os.Stat("database.yml") 503 if err == nil { 504 // yaml already exists, don't do anything 505 return 506 } 507 f, err := os.Create("database.yml") 508 if err != nil { 509 log.Fatal(err) 510 } 511 _, err = f.WriteString(DB_CONFIG) 512 if err != nil { 513 log.Fatal(err) 514 } 515 } 516 }`