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  }`