github.com/wiselike/revel-cmd@v1.2.1/revel/build.go (about)

     1  // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
     2  // Revel Framework source code and usage is governed by a MIT style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/wiselike/revel-cmd/harness"
    14  	"github.com/wiselike/revel-cmd/model"
    15  	"github.com/wiselike/revel-cmd/utils"
    16  )
    17  
    18  var cmdBuild = &Command{
    19  	UsageLine: "revel build [-r [run mode]] [import path] [target path] ",
    20  	Short:     "build a Revel application (e.g. for deployment)",
    21  	Long: `
    22  Build the Revel web application named by the given import path.
    23  This allows it to be deployed and run on a machine that lacks a Go installation.
    24  
    25  For example:
    26  
    27      revel build github.com/revel/examples/chat /tmp/chat
    28  
    29  `,
    30  }
    31  
    32  func init() {
    33  	cmdBuild.RunWith = buildApp
    34  	cmdBuild.UpdateConfig = updateBuildConfig
    35  }
    36  
    37  // The update config updates the configuration command so that it can run.
    38  func updateBuildConfig(c *model.CommandConfig, args []string) bool {
    39  	c.Index = model.BUILD
    40  	if c.Build.TargetPath == "" {
    41  		c.Build.TargetPath = "target"
    42  	}
    43  	if len(args) == 0 && c.Build.ImportPath != "" {
    44  		return true
    45  	}
    46  	// If arguments were passed in then there must be two
    47  	if len(args) < 2 {
    48  		fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long)
    49  		return false
    50  	}
    51  
    52  	c.Build.ImportPath = args[0]
    53  	c.Build.TargetPath = args[1]
    54  	if len(args) > 2 {
    55  		c.Build.Mode = args[2]
    56  	}
    57  	return true
    58  }
    59  
    60  // The main entry point to build application from command line.
    61  func buildApp(c *model.CommandConfig) (err error) {
    62  	appImportPath, destPath, mode := c.ImportPath, c.Build.TargetPath, DefaultRunMode
    63  	if len(c.Build.Mode) > 0 {
    64  		mode = c.Build.Mode
    65  	}
    66  
    67  	// Convert target to absolute path
    68  	c.Build.TargetPath, _ = filepath.Abs(destPath)
    69  	c.Build.Mode = mode
    70  	c.Build.ImportPath = appImportPath
    71  
    72  	revelPaths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
    73  	if err != nil {
    74  		return
    75  	}
    76  
    77  	if err = buildSafetyCheck(destPath); err != nil {
    78  		return
    79  	}
    80  
    81  	// Ensure the application can be built, this generates the main file
    82  	app, err := harness.Build(c, revelPaths)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	// Copy files
    87  	// Included are:
    88  	// - run scripts
    89  	// - binary
    90  	// - revel
    91  	// - app
    92  
    93  	packageFolders, err := buildCopyFiles(c, app, revelPaths)
    94  	if err != nil {
    95  		return
    96  	}
    97  	err = buildCopyModules(c, revelPaths, packageFolders, app)
    98  	if err != nil {
    99  		return
   100  	}
   101  	err = buildWriteScripts(c, app)
   102  	if err != nil {
   103  		return
   104  	}
   105  	return
   106  }
   107  
   108  // Copy the files to the target.
   109  func buildCopyFiles(c *model.CommandConfig, app *harness.App, revelPaths *model.RevelContainer) (packageFolders []string, err error) {
   110  	appImportPath, destPath := c.ImportPath, c.Build.TargetPath
   111  
   112  	// Revel and the app are in a directory structure mirroring import path
   113  	srcPath := filepath.Join(destPath, "src")
   114  	destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath))
   115  	tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(model.RevelImportPath))
   116  	if err = utils.CopyFile(destBinaryPath, filepath.Join(revelPaths.BasePath, app.BinaryPath)); err != nil {
   117  		return
   118  	}
   119  	utils.MustChmod(destBinaryPath, 0755)
   120  
   121  	// Copy the templates  from the revel
   122  	if err = utils.CopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revelPaths.RevelPath, "conf"), nil); err != nil {
   123  		return
   124  	}
   125  	if err = utils.CopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revelPaths.RevelPath, "templates"), nil); err != nil {
   126  		return
   127  	}
   128  
   129  	// Get the folders to be packaged
   130  	packageFolders = strings.Split(revelPaths.Config.StringDefault("package.folders", "conf,public,app/views"), ",")
   131  	for i, p := range packageFolders {
   132  		// Clean spaces, reformat slash to filesystem
   133  		packageFolders[i] = filepath.FromSlash(strings.TrimSpace(p))
   134  	}
   135  
   136  	if c.Build.CopySource {
   137  		err = utils.CopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revelPaths.BasePath, nil)
   138  		if err != nil {
   139  			return
   140  		}
   141  	} else {
   142  		for _, folder := range packageFolders {
   143  			err = utils.CopyDir(
   144  				filepath.Join(srcPath, filepath.FromSlash(appImportPath), folder),
   145  				filepath.Join(revelPaths.BasePath, folder),
   146  				nil)
   147  			if err != nil {
   148  				return
   149  			}
   150  		}
   151  	}
   152  
   153  	return
   154  }
   155  
   156  // Based on the section copy over the build modules.
   157  func buildCopyModules(c *model.CommandConfig, revelPaths *model.RevelContainer, packageFolders []string, app *harness.App) (err error) {
   158  	destPath := filepath.Join(c.Build.TargetPath, "src")
   159  	// Find all the modules used and copy them over.
   160  	config := revelPaths.Config.Raw()
   161  
   162  	// We should only copy over the section of options what the build is targeted for
   163  	// We will default to prod
   164  	moduleImportList := []string{}
   165  	for _, section := range config.Sections() {
   166  		// If the runmode is defined we will only import modules defined for that run mode
   167  		if c.Build.Mode != "" && c.Build.Mode != section {
   168  			continue
   169  		}
   170  		options, _ := config.SectionOptions(section)
   171  		for _, key := range options {
   172  			if !strings.HasPrefix(key, "module.") {
   173  				continue
   174  			}
   175  			moduleImportPath, _ := config.String(section, key)
   176  			if moduleImportPath == "" {
   177  				continue
   178  			}
   179  			moduleImportList = append(moduleImportList, moduleImportPath)
   180  		}
   181  	}
   182  
   183  	// Copy the the paths for each of the modules
   184  	for _, importPath := range moduleImportList {
   185  		fsPath := app.PackagePathMap[importPath]
   186  		utils.Logger.Info("Copy files ", "to", filepath.Join(destPath, importPath), "from", fsPath)
   187  		if c.Build.CopySource {
   188  			err = utils.CopyDir(filepath.Join(destPath, importPath), fsPath, nil)
   189  			if err != nil {
   190  				return
   191  			}
   192  		} else {
   193  			for _, folder := range packageFolders {
   194  				err = utils.CopyDir(
   195  					filepath.Join(destPath, importPath, folder),
   196  					filepath.Join(fsPath, folder),
   197  					nil)
   198  				if err != nil {
   199  					return
   200  				}
   201  			}
   202  		}
   203  	}
   204  
   205  	return
   206  }
   207  
   208  // Write the run scripts for the build.
   209  func buildWriteScripts(c *model.CommandConfig, app *harness.App) (err error) {
   210  	tmplData := map[string]interface{}{
   211  		"BinName":    filepath.Base(app.BinaryPath),
   212  		"ImportPath": c.Build.ImportPath,
   213  		"Mode":       c.Build.Mode,
   214  	}
   215  
   216  	err = utils.GenerateTemplate(
   217  		filepath.Join(c.Build.TargetPath, "run.sh"),
   218  		PACKAGE_RUN_SH,
   219  		tmplData,
   220  	)
   221  	if err != nil {
   222  		return
   223  	}
   224  	utils.MustChmod(filepath.Join(c.Build.TargetPath, "run.sh"), 0755)
   225  	err = utils.GenerateTemplate(
   226  		filepath.Join(c.Build.TargetPath, "run.bat"),
   227  		PACKAGE_RUN_BAT,
   228  		tmplData,
   229  	)
   230  	if err != nil {
   231  		return
   232  	}
   233  
   234  	fmt.Println("Your application has been built in:", c.Build.TargetPath)
   235  
   236  	return
   237  }
   238  
   239  // Checks to see if the target folder exists and can be created.
   240  func buildSafetyCheck(destPath string) error {
   241  	// First, verify that it is either already empty or looks like a previous
   242  	// build (to avoid clobbering anything)
   243  	if utils.Exists(destPath) && !utils.Empty(destPath) && !utils.Exists(filepath.Join(destPath, "run.sh")) {
   244  		return utils.NewBuildError("Abort: %s exists and does not look like a build directory.", "path", destPath)
   245  	}
   246  
   247  	if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
   248  		return utils.NewBuildIfError(err, "Remove all error", "path", destPath)
   249  	}
   250  
   251  	if err := os.MkdirAll(destPath, 0777); err != nil {
   252  		return utils.NewBuildIfError(err, "MkDir all error", "path", destPath)
   253  	}
   254  	return nil
   255  }
   256  
   257  const PACKAGE_RUN_SH = `#!/bin/sh
   258  
   259  SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
   260  "$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}
   261  `
   262  
   263  const PACKAGE_RUN_BAT = `@echo off
   264  
   265  {{.BinName}} -importPath {{.ImportPath}} -srcPath "%CD%\src" -runMode {{.Mode}}
   266  `