github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/cmd/vpm/build.go (about) 1 /* 2 * Copyright (c) 2024-present unTill Pro, Ltd. 3 * @author Alisher Nurmanov 4 */ 5 6 package main 7 8 import ( 9 "errors" 10 "fmt" 11 "github.com/voedger/voedger/pkg/parser" 12 "io" 13 "os" 14 "path/filepath" 15 "strings" 16 17 "github.com/google/uuid" 18 19 "github.com/spf13/cobra" 20 "github.com/voedger/voedger/pkg/compile" 21 "github.com/voedger/voedger/pkg/goutils/exec" 22 "github.com/voedger/voedger/pkg/goutils/logger" 23 coreutils "github.com/voedger/voedger/pkg/utils" 24 ) 25 26 func newBuildCmd(params *vpmParams) *cobra.Command { 27 cmd := &cobra.Command{ 28 Use: "build [-C] [-o <archive-name>]", 29 Short: "build", 30 RunE: func(cmd *cobra.Command, args []string) error { 31 exists, err := checkPackageGenFileExists(params.Dir) 32 if err != nil { 33 return err 34 } 35 if !exists { 36 return errors.New("packages_gen.go not found. Run 'vpm init'") 37 } 38 39 compileRes, err := compile.CompileNoDummyApp(params.Dir) 40 if err := checkAppSchemaNotFoundErr(err); err != nil { 41 return err 42 } 43 if err := checkCompileResult(compileRes); err != nil { 44 return err 45 } 46 return build(compileRes, params) 47 }, 48 } 49 cmd.SilenceErrors = true 50 cmd.Flags().StringVarP(¶ms.Dir, "change-dir", "C", "", "Change to dir before running the command. Any files named on the command line are interpreted after changing directories. If used, this flag must be the first one in the command line.") 51 cmd.Flags().StringVarP(¶ms.Output, "output", "o", "", "output archive name") 52 return cmd 53 } 54 55 func checkAppSchemaNotFoundErr(err error) error { 56 if err != nil { 57 logger.Error(err) 58 if errors.Is(err, compile.ErrAppSchemaNotFound) { 59 return errors.New("failed to build, app schema not found") 60 } 61 } 62 return nil 63 } 64 65 func checkCompileResult(compileRes *compile.Result) error { 66 switch { 67 case compileRes == nil: 68 return errors.New("failed to compile, check schemas") 69 case len(compileRes.NotFoundDeps) > 0: 70 return errors.New("failed to compile, missing dependencies. Run 'vpm tidy'") 71 default: 72 return nil 73 } 74 } 75 76 func build(compileRes *compile.Result, params *vpmParams) error { 77 // temp directory to save the build info: vsql files, wasm files 78 tempBuildInfoDir := filepath.Join(os.TempDir(), uuid.New().String(), buildDirName) 79 if err := os.MkdirAll(tempBuildInfoDir, coreutils.FileMode_rwxrwxrwx); err != nil { 80 return err 81 } 82 // create temp build info directory along with vsql and wasm files 83 if err := buildDir(compileRes.PkgFiles, tempBuildInfoDir); err != nil { 84 return err 85 } 86 // set the path to the output archive, e.g. app.var 87 archiveName := params.Output 88 if archiveName == "" { 89 archiveName = filepath.Base(params.Dir) 90 } 91 if !strings.HasSuffix(archiveName, ".var") { 92 archiveName += ".var" 93 } 94 archivePath := filepath.Join(params.Dir, archiveName) 95 96 // zip build info directory along with vsql and wasm files 97 return coreutils.Zip(archivePath, tempBuildInfoDir) 98 } 99 100 // buildDir creates a directory structure with vsql and wasm files 101 func buildDir(pkgFiles packageFiles, buildDirPath string) error { 102 for qpn, files := range pkgFiles { 103 pkgBuildDir := filepath.Join(buildDirPath, qpn) 104 if err := os.MkdirAll(pkgBuildDir, coreutils.FileMode_rwxrwxrwx); err != nil { 105 return err 106 } 107 108 for _, file := range files { 109 // copy vsql files 110 base := filepath.Base(file) 111 fileNameExtensionless := base[:len(base)-len(filepath.Ext(base))] 112 if err := coreutils.CopyFile(file, pkgBuildDir, coreutils.WithNewName(fileNameExtensionless+parser.VSqlExt)); err != nil { 113 return fmt.Errorf(errFmtCopyFile, file, err) 114 } 115 116 // building wasm files: if wasm directory exists, build wasm file and copy it to the temp build directory 117 fileDir := filepath.Dir(file) 118 wasmDirPath := filepath.Join(fileDir, wasmDirName) 119 exists, err := coreutils.Exists(wasmDirPath) 120 if err != nil { 121 return err 122 } 123 if exists { 124 appName := filepath.Base(fileDir) 125 wasmFilePath, err := execTinyGoBuild(wasmDirPath, appName) 126 if err != nil { 127 return err 128 } 129 if err := coreutils.CopyFile(wasmFilePath, pkgBuildDir); err != nil { 130 return fmt.Errorf(errFmtCopyFile, wasmFilePath, err) 131 } 132 // remove the wasm file after copying it to the build directory 133 if err := os.Remove(wasmFilePath); err != nil { 134 return err 135 } 136 } 137 138 } 139 } 140 return nil 141 } 142 143 // execTinyGoBuild builds the project using tinygo and returns the path to the resulting wasm file 144 func execTinyGoBuild(dir, appName string) (wasmFilePath string, err error) { 145 var stdout io.Writer 146 if logger.IsVerbose() { 147 stdout = os.Stdout 148 } 149 150 wasmFileName := appName + ".wasm" 151 if err := new(exec.PipedExec).Command("tinygo", "build", "--no-debug", "-o", wasmFileName, "-scheduler=none", "-opt=2", "-gc=leaking", "-target=wasi", ".").WorkingDir(dir).Run(stdout, os.Stderr); err != nil { 152 return "", err 153 } 154 return filepath.Join(dir, wasmFileName), nil 155 }