github.com/jd-ly/cmd@v1.0.10/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  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"fmt"
    13  	"github.com/jd-ly/cmd/harness"
    14  	"github.com/jd-ly/cmd/model"
    15  	"github.com/jd-ly/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  
    63  	appImportPath, destPath, mode := c.ImportPath, c.Build.TargetPath, DefaultRunMode
    64  	if len(c.Build.Mode) > 0 {
    65  		mode = c.Build.Mode
    66  	}
    67  
    68  	// Convert target to absolute path
    69  	c.Build.TargetPath, _ = filepath.Abs(destPath)
    70  	c.Build.Mode = mode
    71  	c.Build.ImportPath = appImportPath
    72  
    73  	revel_paths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
    74  	if err != nil {
    75  		return
    76  	}
    77  
    78  	if err = buildSafetyCheck(destPath); err != nil {
    79  		return
    80  	}
    81  
    82  	// Ensure the application can be built, this generates the main file
    83  	app, err := harness.Build(c, revel_paths)
    84  	if err != nil {
    85  		return err
    86  	}
    87  	// Copy files
    88  	// Included are:
    89  	// - run scripts
    90  	// - binary
    91  	// - revel
    92  	// - app
    93  
    94  	packageFolders, err := buildCopyFiles(c, app, revel_paths)
    95  	if err != nil {
    96  		return
    97  	}
    98  	err = buildCopyModules(c, revel_paths, packageFolders, app)
    99  	if err != nil {
   100  		return
   101  	}
   102  	err = buildWriteScripts(c, app)
   103  	if err != nil {
   104  		return
   105  	}
   106  	return
   107  }
   108  
   109  // Copy the files to the target
   110  func buildCopyFiles(c *model.CommandConfig, app *harness.App, revel_paths *model.RevelContainer) (packageFolders []string, err error) {
   111  	appImportPath, destPath := c.ImportPath, c.Build.TargetPath
   112  
   113  	// Revel and the app are in a directory structure mirroring import path
   114  	srcPath := filepath.Join(destPath, "src")
   115  	destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath))
   116  	tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(model.RevelImportPath))
   117  	if err = utils.CopyFile(destBinaryPath, filepath.Join(revel_paths.BasePath, app.BinaryPath)); err != nil {
   118  		return
   119  	}
   120  	utils.MustChmod(destBinaryPath, 0755)
   121  
   122  	// Copy the templates  from the revel
   123  	if err = utils.CopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revel_paths.RevelPath, "conf"), nil); err != nil {
   124  		return
   125  	}
   126  	if err = utils.CopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revel_paths.RevelPath, "templates"), nil); err != nil {
   127  		return
   128  	}
   129  
   130  	// Get the folders to be packaged
   131  	packageFolders = strings.Split(revel_paths.Config.StringDefault("package.folders", "conf,public,app/views"), ",")
   132  	for i, p := range packageFolders {
   133  		// Clean spaces, reformat slash to filesystem
   134  		packageFolders[i] = filepath.FromSlash(strings.TrimSpace(p))
   135  	}
   136  
   137  	if c.Build.CopySource {
   138  		err = utils.CopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revel_paths.BasePath, nil)
   139  		if err != nil {
   140  			return
   141  		}
   142  	} else {
   143  		for _, folder := range packageFolders {
   144  			err = utils.CopyDir(
   145  				filepath.Join(srcPath, filepath.FromSlash(appImportPath), folder),
   146  				filepath.Join(revel_paths.BasePath, folder),
   147  				nil)
   148  			if err != nil {
   149  				return
   150  			}
   151  		}
   152  	}
   153  
   154  	return
   155  }
   156  
   157  // Based on the section copy over the build modules
   158  func buildCopyModules(c *model.CommandConfig, revel_paths *model.RevelContainer, packageFolders []string, app *harness.App) (err error) {
   159  	destPath := filepath.Join(c.Build.TargetPath, "src")
   160  	// Find all the modules used and copy them over.
   161  	config := revel_paths.Config.Raw()
   162  
   163  	// We should only copy over the section of options what the build is targeted for
   164  	// We will default to prod
   165  	moduleImportList := []string{}
   166  	for _, section := range config.Sections() {
   167  		// If the runmode is defined we will only import modules defined for that run mode
   168  		if c.Build.Mode != "" && c.Build.Mode != section {
   169  			continue
   170  		}
   171  		options, _ := config.SectionOptions(section)
   172  		for _, key := range options {
   173  			if !strings.HasPrefix(key, "module.") {
   174  				continue
   175  			}
   176  			moduleImportPath, _ := config.String(section, key)
   177  			if moduleImportPath == "" {
   178  				continue
   179  			}
   180  			moduleImportList = append(moduleImportList, moduleImportPath)
   181  
   182  		}
   183  	}
   184  
   185  	// Copy the the paths for each of the modules
   186  	for _, importPath := range moduleImportList {
   187  		fsPath := app.PackagePathMap[importPath]
   188  		utils.Logger.Info("Copy files ", "to", filepath.Join(destPath, importPath), "from", fsPath)
   189  		if c.Build.CopySource {
   190  			err = utils.CopyDir(filepath.Join(destPath, importPath), fsPath, nil)
   191  			if err != nil {
   192  				return
   193  			}
   194  		} else {
   195  			for _, folder := range packageFolders {
   196  				err = utils.CopyDir(
   197  					filepath.Join(destPath, importPath, folder),
   198  					filepath.Join(fsPath, folder),
   199  					nil)
   200  				if err != nil {
   201  					return
   202  				}
   203  			}
   204  		}
   205  	}
   206  
   207  	return
   208  }
   209  
   210  // Write the run scripts for the build
   211  func buildWriteScripts(c *model.CommandConfig, app *harness.App) (err error) {
   212  	tmplData := map[string]interface{}{
   213  		"BinName":    filepath.Base(app.BinaryPath),
   214  		"ImportPath": c.Build.ImportPath,
   215  		"Mode":       c.Build.Mode,
   216  	}
   217  
   218  	err = utils.GenerateTemplate(
   219  		filepath.Join(c.Build.TargetPath, "run.sh"),
   220  		PACKAGE_RUN_SH,
   221  		tmplData,
   222  	)
   223  	if err != nil {
   224  		return
   225  	}
   226  	utils.MustChmod(filepath.Join(c.Build.TargetPath, "run.sh"), 0755)
   227  	err = utils.GenerateTemplate(
   228  		filepath.Join(c.Build.TargetPath, "run.bat"),
   229  		PACKAGE_RUN_BAT,
   230  		tmplData,
   231  	)
   232  	if err != nil {
   233  		return
   234  	}
   235  
   236  	fmt.Println("Your application has been built in:", c.Build.TargetPath)
   237  
   238  	return
   239  }
   240  
   241  // Checks to see if the target folder exists and can be created
   242  func buildSafetyCheck(destPath string) error {
   243  
   244  	// First, verify that it is either already empty or looks like a previous
   245  	// build (to avoid clobbering anything)
   246  	if utils.Exists(destPath) && !utils.Empty(destPath) && !utils.Exists(filepath.Join(destPath, "run.sh")) {
   247  		return utils.NewBuildError("Abort: %s exists and does not look like a build directory.", "path", destPath)
   248  	}
   249  
   250  	if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
   251  		return utils.NewBuildIfError(err, "Remove all error", "path", destPath)
   252  	}
   253  
   254  	if err := os.MkdirAll(destPath, 0777); err != nil {
   255  		return utils.NewBuildIfError(err, "MkDir all error", "path", destPath)
   256  	}
   257  	return nil
   258  }
   259  
   260  const PACKAGE_RUN_SH = `#!/bin/sh
   261  
   262  SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
   263  "$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}
   264  `
   265  const PACKAGE_RUN_BAT = `@echo off
   266  
   267  {{.BinName}} -importPath {{.ImportPath}} -srcPath "%CD%\src" -runMode {{.Mode}}
   268  `