github.com/gobuffalo/buffalo-cli/v2@v2.0.0-alpha.15.0.20200919213536-a7350c8e6799/cli/cmds/build/main_file.go (about)

     1  package build
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"go/ast"
     8  	"go/parser"
     9  	"go/printer"
    10  	"go/token"
    11  	"io"
    12  	"os"
    13  	"os/exec"
    14  	"path"
    15  	"path/filepath"
    16  	"sort"
    17  	"strconv"
    18  	"strings"
    19  	"text/template"
    20  	"time"
    21  
    22  	"github.com/gobuffalo/here"
    23  	"github.com/gobuffalo/plugins"
    24  	"github.com/gobuffalo/plugins/plugprint"
    25  	"golang.org/x/tools/go/ast/astutil"
    26  )
    27  
    28  const mainBuildFile = "main.build.go"
    29  
    30  var _ AfterBuilder = &MainFile{}
    31  var _ BeforeBuilder = &MainFile{}
    32  var _ plugins.Plugin = &MainFile{}
    33  var _ plugins.Needer = &MainFile{}
    34  var _ plugins.Scoper = &MainFile{}
    35  var _ plugprint.Hider = &MainFile{}
    36  
    37  type MainFile struct {
    38  	pluginsFn         plugins.Feeder
    39  	withFallthroughFn func() bool
    40  }
    41  
    42  func (bc *MainFile) WithPlugins(f plugins.Feeder) {
    43  	bc.pluginsFn = f
    44  }
    45  
    46  func (MainFile) HidePlugin() {}
    47  
    48  func (MainFile) PluginName() string {
    49  	return "main"
    50  }
    51  
    52  func (bc *MainFile) ScopedPlugins() []plugins.Plugin {
    53  	if bc.pluginsFn == nil {
    54  		return nil
    55  	}
    56  	return bc.pluginsFn()
    57  }
    58  
    59  func (bc *MainFile) Version(ctx context.Context, root string) (string, error) {
    60  	versions := map[string]string{
    61  		"time": time.Now().Format(time.RFC3339),
    62  	}
    63  	m := func() (string, error) {
    64  		b, err := json.Marshal(versions)
    65  		if err != nil {
    66  			return "", plugins.Wrap(bc, err)
    67  		}
    68  		return string(b), nil
    69  	}
    70  
    71  	for _, p := range bc.ScopedPlugins() {
    72  		bv, ok := p.(Versioner)
    73  		if !ok {
    74  			continue
    75  		}
    76  
    77  		s, err := bv.BuildVersion(ctx, root)
    78  		if err != nil {
    79  			return "", plugins.Wrap(p, err)
    80  		}
    81  		if len(s) == 0 {
    82  			continue
    83  		}
    84  		versions[p.PluginName()] = strings.TrimSpace(s)
    85  	}
    86  	return m()
    87  }
    88  
    89  func (bc *MainFile) generateNewMain(ctx context.Context, info here.Info, version string, ws ...io.Writer) error {
    90  	var imports []string
    91  	for _, p := range bc.ScopedPlugins() {
    92  		bi, ok := p.(Importer)
    93  		if !ok {
    94  			continue
    95  		}
    96  
    97  		i, err := bi.BuildImports(ctx, info.Dir)
    98  		if err != nil {
    99  			return plugins.Wrap(p, err)
   100  		}
   101  		imports = append(imports, i...)
   102  	}
   103  
   104  	if i, err := here.Dir(filepath.Join(info.Dir, "actions")); err == nil {
   105  		imports = append(imports, i.ImportPath)
   106  	}
   107  
   108  	sort.Strings(imports)
   109  
   110  	bt := struct {
   111  		BuildTime       string
   112  		BuildVersion    string
   113  		Imports         []string
   114  		Info            here.Info
   115  		WithFallthrough bool
   116  	}{
   117  		BuildTime:    strconv.Quote(time.Now().Format(time.RFC3339)),
   118  		BuildVersion: strconv.Quote(version),
   119  		Imports:      imports,
   120  		Info:         info,
   121  	}
   122  
   123  	ft := bc.withFallthroughFn
   124  	if ft == nil {
   125  		ft = func() bool {
   126  			c := exec.CommandContext(ctx, "go", "doc", path.Join(info.ImportPath, "cli")+".Buffalo")
   127  			err := c.Run()
   128  			return err == nil
   129  		}
   130  	}
   131  	bt.WithFallthrough = ft()
   132  
   133  	t, err := template.New(mainBuildFile).Parse(mainBuildTmpl)
   134  	if err != nil {
   135  		return plugins.Wrap(bc, err)
   136  	}
   137  
   138  	f, err := os.Create(filepath.Join(info.Dir, mainBuildFile))
   139  	if err != nil {
   140  		return plugins.Wrap(bc, err)
   141  	}
   142  	defer f.Close()
   143  
   144  	if err := t.Execute(io.MultiWriter(append(ws, f)...), bt); err != nil {
   145  		return plugins.Wrap(bc, err)
   146  	}
   147  	return nil
   148  }
   149  
   150  func (bc *MainFile) BeforeBuild(ctx context.Context, root string, args []string) error {
   151  	info, err := bc.binaryFolderInfo(root)
   152  	if err != nil {
   153  		return plugins.Wrap(bc, err)
   154  	}
   155  
   156  	err = bc.renameMain(info, "main", "originalMain")
   157  	if err != nil {
   158  		return plugins.Wrap(bc, err)
   159  	}
   160  
   161  	version, err := bc.Version(ctx, info.Dir)
   162  	if err != nil {
   163  		return plugins.Wrap(bc, err)
   164  	}
   165  
   166  	if err := bc.generateNewMain(ctx, info, version); err != nil {
   167  		return plugins.Wrap(bc, err)
   168  	}
   169  	return nil
   170  }
   171  
   172  var _ AfterBuilder = &MainFile{}
   173  
   174  func (bc *MainFile) AfterBuild(ctx context.Context, root string, args []string, err error) error {
   175  	info, err := bc.binaryFolderInfo(root)
   176  	if err != nil {
   177  		return plugins.Wrap(bc, err)
   178  	}
   179  
   180  	os.RemoveAll(filepath.Join(info.Dir, mainBuildFile))
   181  	err = bc.renameMain(info, "originalMain", "main")
   182  	return plugins.Wrap(bc, err)
   183  }
   184  
   185  func (bc *MainFile) binaryFolderInfo(root string) (here.Info, error) {
   186  	rinfo, err := here.Dir(root)
   187  	if err != nil {
   188  		return here.Info{}, err
   189  	}
   190  
   191  	info, err := here.Dir(filepath.Join("cmd", strings.ToLower(rinfo.Name)))
   192  	return info, err
   193  }
   194  
   195  func (bc *MainFile) renameMain(info here.Info, from string, to string) error {
   196  	if !info.Module.Main {
   197  		err := fmt.Errorf("module %s is not a main", info.Name)
   198  		return plugins.Wrap(bc, err)
   199  	}
   200  
   201  	fset := token.NewFileSet()
   202  	pkgs, err := parser.ParseDir(fset, info.Dir, nil, 0)
   203  	if err != nil {
   204  		return plugins.Wrap(bc, err)
   205  	}
   206  
   207  	for _, p := range pkgs {
   208  		for x, f := range p.Files {
   209  
   210  			err := func() error {
   211  				var reprint bool
   212  				pre := func(c *astutil.Cursor) bool {
   213  					n := c.Name()
   214  					if n != "Decls" {
   215  						return true
   216  					}
   217  
   218  					fd, ok := c.Node().(*ast.FuncDecl)
   219  					if !ok {
   220  						return true
   221  					}
   222  
   223  					n = fd.Name.Name
   224  					if n != from {
   225  						return true
   226  					}
   227  
   228  					fd.Name = ast.NewIdent(to)
   229  					c.Replace(fd)
   230  					reprint = true
   231  					return true
   232  				}
   233  
   234  				res := astutil.Apply(f, pre, nil)
   235  				if !reprint {
   236  					return nil
   237  				}
   238  
   239  				f, err := os.Create(x)
   240  				if err != nil {
   241  					return plugins.Wrap(bc, err)
   242  				}
   243  				defer f.Close()
   244  				err = printer.Fprint(f, fset, res)
   245  				return plugins.Wrap(bc, err)
   246  			}()
   247  
   248  			if err != nil {
   249  				return plugins.Wrap(bc, err)
   250  			}
   251  		}
   252  	}
   253  	return nil
   254  }