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