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 }