github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/cmd/vpm/compat.go (about) 1 /* 2 * Copyright (c) 2023-present unTill Pro, Ltd. 3 * @author Alisher Nurmanov 4 */ 5 6 package main 7 8 import ( 9 "errors" 10 "fmt" 11 "os" 12 "path/filepath" 13 "strings" 14 15 "github.com/spf13/cobra" 16 "gopkg.in/yaml.v2" 17 18 "github.com/voedger/voedger/pkg/appdef" 19 "github.com/voedger/voedger/pkg/appdefcompat" 20 "github.com/voedger/voedger/pkg/compile" 21 "github.com/voedger/voedger/pkg/parser" 22 coreutils "github.com/voedger/voedger/pkg/utils" 23 ) 24 25 func newCompatCmd(params *vpmParams) *cobra.Command { 26 cmd := &cobra.Command{ 27 Use: "compat baseline-folder", 28 Short: "check backward compatibility", 29 Args: exactArgs(1), 30 RunE: func(cmd *cobra.Command, args []string) (err error) { 31 ignores, err := readIgnoreFile(params.IgnoreFile) 32 if err != nil { 33 return err 34 } 35 compileRes, err := compile.Compile(params.Dir) 36 if err != nil { 37 return err 38 } 39 return compat(compileRes, params, ignores) 40 }, 41 } 42 cmd.Flags().StringVarP(¶ms.IgnoreFile, "ignore", "", "", "path to yaml file which contains list of errors to be ignored") 43 return cmd 44 } 45 46 // compat checks compatibility of schemas in dir versus baseline schemas in target dir 47 func compat(compileRes *compile.Result, params *vpmParams, ignores [][]string) error { 48 baselineDir := params.TargetDir 49 var errs []error 50 baselineAppDef, err := appDefFromBaselineDir(baselineDir) 51 if err != nil { 52 errs = append(errs, coreutils.SplitErrors(err)...) 53 } 54 55 if baselineAppDef != nil && compileRes.AppDef != nil { 56 compatErrs := appdefcompat.CheckBackwardCompatibility(baselineAppDef, compileRes.AppDef) 57 compatErrs = appdefcompat.IgnoreCompatibilityErrors(compatErrs, ignores) 58 errObjs := make([]error, len(compatErrs.Errors)) 59 for i, err := range compatErrs.Errors { 60 errObjs[i] = err 61 } 62 errs = append(errs, errObjs...) 63 } 64 return errors.Join(errs...) 65 } 66 67 // readIgnoreFile reads yaml file and returns list of errors to be ignored 68 func readIgnoreFile(ignoreFilePath string) ([][]string, error) { 69 if ignoreFilePath != "" { 70 content, err := os.ReadFile(ignoreFilePath) 71 if err != nil { 72 return nil, err 73 } 74 75 var ignoreInfoObj ignoreInfo 76 if err := yaml.Unmarshal(content, &ignoreInfoObj); err != nil { 77 return nil, err 78 } 79 return splitIgnorePaths(ignoreInfoObj.Ignore), nil 80 } 81 return nil, nil 82 } 83 84 // appDefFromBaselineDir builds app def from baseline dir 85 func appDefFromBaselineDir(baselineDir string) (appdef.IAppDef, error) { 86 var errs []error 87 88 pkgDirPath := filepath.Join(baselineDir, pkgDirName) 89 pkgDirPathExists, err := coreutils.Exists(pkgDirPath) 90 if err != nil { 91 // notest 92 return nil, err 93 } 94 if !pkgDirPathExists { 95 return nil, fmt.Errorf("baseline directory does not contain %s subdirectory", pkgDirName) 96 } 97 baselineJsonFilePath := filepath.Join(baselineDir, baselineInfoFileName) 98 baselineJsonFilePathExists, err := coreutils.Exists(baselineJsonFilePath) 99 if err != nil { 100 // notest 101 return nil, err 102 } 103 if !baselineJsonFilePathExists { 104 return nil, fmt.Errorf("baseline directory does not contain %s file", baselineInfoFileName) 105 } 106 107 // gather schema files from baseline dir 108 var schemaFiles []string 109 if err := filepath.Walk(pkgDirPath, func(path string, info os.FileInfo, err error) error { 110 if err != nil { 111 return err 112 } 113 if !info.IsDir() && filepath.Ext(path) == parser.VSqlExt { 114 schemaFiles = append(schemaFiles, path) 115 } 116 return nil 117 }); err != nil { 118 errs = append(errs, err) 119 } 120 121 // form package files structure 122 pkgFiles := make(packageFiles) 123 for _, schemaFile := range schemaFiles { 124 dir := filepath.Dir(schemaFile) 125 qpn, err := filepath.Rel(pkgDirPath, dir) 126 if err != nil { 127 return nil, err 128 } 129 qpn = strings.ReplaceAll(qpn, "\\", "/") 130 pkgFiles[qpn] = append(pkgFiles[qpn], schemaFile) 131 } 132 133 // build package ASTs from schema files 134 packageASTs := make([]*parser.PackageSchemaAST, 0) 135 for qpn, files := range pkgFiles { 136 // build file ASTs 137 var fileASTs []*parser.FileSchemaAST 138 for _, file := range files { 139 content, err := os.ReadFile(file) 140 if err != nil { 141 errs = append(errs, err) 142 } 143 fileName := filepath.Base(file) 144 145 fileAST, err := parser.ParseFile(fileName, string(content)) 146 if err != nil { 147 errs = append(errs, err) 148 } 149 fileASTs = append(fileASTs, fileAST) 150 } 151 152 // build package AST 153 packageAST, err := parser.BuildPackageSchema(qpn, fileASTs) 154 if err != nil { 155 errs = append(errs, err) 156 } 157 // add package AST to list 158 packageASTs = append(packageASTs, packageAST) 159 } 160 161 // build app AST 162 appAST, err := parser.BuildAppSchema(packageASTs) 163 if err != nil { 164 errs = append(errs, err) 165 } 166 // build app def from app AST 167 if appAST != nil { 168 builder := appdef.New() 169 if err := parser.BuildAppDefs(appAST, builder); err != nil { 170 errs = append(errs, err) 171 } 172 appDef, err := builder.Build() 173 if err != nil { 174 errs = append(errs, err) 175 } 176 return appDef, errors.Join(errs...) 177 } 178 return nil, errors.Join(errs...) 179 } 180 181 // splitIgnorePaths splits list of ignore paths into list of path parts 182 func splitIgnorePaths(ignores []string) (res [][]string) { 183 res = make([][]string, len(ignores)) 184 for i, ignore := range ignores { 185 res[i] = strings.Split(ignore, "/") 186 } 187 return 188 }