github.com/torresashjian/cli@v0.10.1-0.20210916231452-89080fe7069c/util/mod.go (about) 1 package util 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "log" 10 "net/http" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "regexp" 15 "strings" 16 17 "github.com/msoap/byline" 18 ) 19 20 type DepManager interface { 21 Init() error 22 AddDependency(flogoImport Import) error 23 GetPath(flogoImport Import) (string, error) 24 AddReplacedContribForBuild() error 25 InstallReplacedPkg(string, string) error 26 GetAllImports() (map[string]Import, error) 27 } 28 29 func NewDepManager(sourceDir string) DepManager { 30 return &ModDepManager{srcDir: sourceDir, localMods: make(map[string]string)} 31 } 32 33 type ModDepManager struct { 34 srcDir string 35 localMods map[string]string 36 } 37 38 func (m *ModDepManager) Init() error { 39 40 err := ExecCmd(exec.Command("go", "mod", "init", "main"), m.srcDir) 41 if err == nil { 42 return err 43 } 44 45 return nil 46 } 47 48 func (m *ModDepManager) AddDependency(flogoImport Import) error { 49 50 // todo: optimize the following 51 52 // use "go mod edit" (instead of "go get") as first method 53 err := ExecCmd(exec.Command("go", "mod", "edit", "-require", flogoImport.GoModImportPath()), m.srcDir) 54 if err != nil { 55 return err 56 } 57 58 59 err = ExecCmd(exec.Command("go", "mod", "verify"), m.srcDir) 60 if err == nil { 61 err = ExecCmd(exec.Command("go", "mod", "download", flogoImport.ModulePath()), m.srcDir) 62 } 63 64 if err != nil { 65 // if the resolution fails and the Flogo import is "classic" 66 // (meaning it does not separate module path from Go import path): 67 // 1. remove the import manually ("go mod edit -droprequire") would fail 68 // 2. try with "go get" instead 69 if flogoImport.IsClassic() { 70 m.RemoveImport(flogoImport) 71 72 err = ExecCmd(exec.Command("go", "get", flogoImport.GoGetImportPath()), m.srcDir) 73 } 74 } 75 76 if err != nil { 77 return err 78 } 79 80 return nil 81 } 82 83 // GetPath gets the path of where the 84 func (m *ModDepManager) GetPath(flogoImport Import) (string, error) { 85 86 currentDir, err := os.Getwd() 87 if err != nil { 88 return "", err 89 } 90 91 pkg := flogoImport.ModulePath() 92 93 path, ok := m.localMods[pkg] 94 if ok && path != "" { 95 96 return path, nil 97 } 98 defer os.Chdir(currentDir) 99 100 os.Chdir(m.srcDir) 101 102 file, err := os.Open(filepath.Join(m.srcDir, "go.mod")) 103 defer file.Close() 104 105 var pathForPartial string 106 107 scanner := bufio.NewScanner(file) 108 for scanner.Scan() { 109 110 line := scanner.Text() 111 reqComponents := strings.Fields(line) 112 //It is the line in the go.mod which is not useful, so ignore. 113 if len(reqComponents) < 2 || (reqComponents[0] == "require" && reqComponents[1] == "(") { 114 continue 115 } 116 117 //typically package is 1st component and version is the 2nd component 118 reqPkg := reqComponents[0] 119 version := reqComponents[1] 120 if reqComponents[0] == "require" { 121 //starts with require, so package is 2nd component and version is the 3rd component 122 reqPkg = reqComponents[1] 123 version = reqComponents[2] 124 } 125 126 if strings.HasPrefix(pkg, reqPkg) { 127 128 hasFull := strings.Contains(line, pkg) 129 tempPath := strings.Split(reqPkg, "/") 130 131 tempPath = toLower(tempPath) 132 lastIdx := len(tempPath) - 1 133 134 tempPath[lastIdx] = tempPath[lastIdx] + "@" + version 135 136 pkgPath := filepath.Join(tempPath...) 137 138 if !hasFull { 139 remaining := pkg[len(reqPkg):] 140 tempPath = strings.Split(remaining, "/") 141 remainingPath := filepath.Join(tempPath...) 142 143 pathForPartial = filepath.Join(os.Getenv("GOPATH"), "pkg", "mod", pkgPath, remainingPath) 144 } else { 145 return filepath.Join(os.Getenv("GOPATH"), "pkg", "mod", pkgPath, flogoImport.RelativeImportPath()), nil 146 } 147 } 148 } 149 return pathForPartial, nil 150 } 151 152 func (m *ModDepManager) RemoveImport(flogoImport Import) error { 153 154 currentDir, err := os.Getwd() 155 if err != nil { 156 return err 157 } 158 159 modulePath := flogoImport.ModulePath() 160 161 defer os.Chdir(currentDir) 162 163 os.Chdir(m.srcDir) 164 165 file, err := os.Open(filepath.Join(m.srcDir, "go.mod")) 166 if err != nil { 167 return err 168 } 169 defer file.Close() 170 171 modulePath = strings.Replace(modulePath, "/", "\\/", -1) 172 modulePath = strings.Replace(modulePath, ".", "\\.", -1) 173 importRegex := regexp.MustCompile(`\s*` + modulePath + `\s+` + flogoImport.Version() + `.*`) 174 175 lr := byline.NewReader(file) 176 177 lr.MapString(func(line string) string { 178 if importRegex.MatchString(line) { 179 return "" 180 } else { 181 return line 182 } 183 }) 184 185 updatedGoMod, err := lr.ReadAll() 186 if err != nil { 187 return err 188 } 189 190 file, err = os.Create(filepath.Join(m.srcDir, "go.mod")) 191 if err != nil { 192 return err 193 } 194 defer file.Close() 195 196 file.Write(updatedGoMod) 197 198 return nil 199 } 200 func (m *ModDepManager) GetAllImports() (map[string]Import, error) { 201 file, err := ioutil.ReadFile(filepath.Join(m.srcDir, "go.mod")) 202 if err != nil { 203 return nil, err 204 } 205 206 content := string(file) 207 208 imports := strings.Split(content[strings.Index(content, "(")+1:strings.Index(content, ")")], "\n") 209 result := make(map[string]Import) 210 211 for _, pkg := range imports { 212 if pkg != " " && pkg != "" { 213 214 mods := strings.Split(strings.TrimSpace(pkg), " ") 215 216 modImport, err := ParseImport(strings.Join(mods[:2], "@")) 217 if err != nil { 218 return nil, err 219 } 220 221 result[modImport.GoImportPath()] = modImport 222 } 223 } 224 225 return result, nil 226 } 227 228 //This function converts capotal letters in package name 229 // to !(smallercase). Eg C => !c . As this is the way 230 // go.mod saves every repository in the $GOPATH/pkg/mod. 231 func toLower(s []string) []string { 232 result := make([]string, len(s)) 233 for i := 0; i < len(s); i++ { 234 var b bytes.Buffer 235 for _, c := range s[i] { 236 if c >= 65 && c <= 90 { 237 b.WriteRune(33) 238 b.WriteRune(c + 32) 239 } else { 240 b.WriteRune(c) 241 } 242 } 243 result[i] = b.String() 244 } 245 return result 246 } 247 248 var verbose = false 249 250 func SetVerbose(enable bool) { 251 verbose = enable 252 } 253 254 func Verbose() bool { 255 return verbose 256 } 257 258 func ExecCmd(cmd *exec.Cmd, workingDir string) error { 259 260 if workingDir != "" { 261 cmd.Dir = workingDir 262 } 263 264 var out bytes.Buffer 265 266 if verbose { 267 cmd.Stdout = os.Stdout 268 cmd.Stderr = os.Stderr 269 } else { 270 cmd.Stdout = nil 271 cmd.Stderr = &out 272 } 273 274 err := cmd.Run() 275 276 if err != nil { 277 return fmt.Errorf(string(out.Bytes())) 278 } 279 280 return nil 281 } 282 283 func (m *ModDepManager) AddReplacedContribForBuild() error { 284 285 err := ExecCmd(exec.Command("go", "mod", "download"), m.srcDir) 286 if err != nil { 287 return err 288 } 289 290 text, err := ioutil.ReadFile(filepath.Join(m.srcDir, "go.mod")) 291 if err != nil { 292 return err 293 } 294 295 data := string(text) 296 297 index := strings.Index(data, "replace") 298 if index != -1 { 299 localModules := strings.Split(data[index-1:], "\n") 300 301 for _, val := range localModules { 302 if val != "" { 303 mods := strings.Split(val, " ") 304 //If the length of mods is more than 4 it contains the versions of package 305 //so it is stating to use different version of pkg rather than 306 // the local pkg. 307 if len(mods) < 5 { 308 309 m.localMods[mods[1]] = mods[3] 310 } else { 311 312 m.localMods[mods[1]] = filepath.Join(os.Getenv("GOPATH"), "pkg", "mod", mods[3]+"@"+mods[4]) 313 } 314 315 } 316 317 } 318 return nil 319 } 320 return nil 321 } 322 323 func (m *ModDepManager) InstallReplacedPkg(pkg1 string, pkg2 string) error { 324 325 m.localMods[pkg1] = pkg2 326 327 f, err := os.OpenFile(filepath.Join(m.srcDir, "go.mod"), os.O_APPEND|os.O_WRONLY, 0777) 328 if err != nil { 329 return err 330 } 331 defer f.Close() 332 333 if _, err = f.WriteString(fmt.Sprintf("replace %v => %v", pkg1, pkg2)); err != nil { 334 return err 335 } 336 337 err = ExecCmd(exec.Command("go", "mod", "download"), m.srcDir) 338 if err != nil { 339 return err 340 } 341 return nil 342 } 343 344 type Resp struct { 345 Name string `json:"name"` 346 } 347 348 func getLatestVersion(path string) string { 349 350 //To get the latest version number use the GitHub API. 351 resp, err := http.Get("https://api.github.com/repos/TIBCOSoftware/flogo-contrib/releases/latest") 352 if err != nil { 353 log.Fatalln(err) 354 } 355 356 body, err := ioutil.ReadAll(resp.Body) 357 if err != nil { 358 log.Fatalln(err) 359 } 360 361 var result Resp 362 363 err = json.Unmarshal(body, &result) 364 if err != nil { 365 return "" 366 } 367 368 return result.Name 369 370 }