github.com/jfrog/jfrog-cli-core@v1.12.1/artifactory/utils/dotnet/dependencies/packagesconfig.go (about) 1 package dependencies 2 3 import ( 4 "encoding/xml" 5 "fmt" 6 gofrogcmd "github.com/jfrog/gofrog/io" 7 "github.com/jfrog/jfrog-cli-core/artifactory/utils/dotnet" 8 "github.com/jfrog/jfrog-client-go/artifactory/buildinfo" 9 "github.com/jfrog/jfrog-client-go/utils/errorutils" 10 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 11 "github.com/jfrog/jfrog-client-go/utils/log" 12 "os" 13 "path/filepath" 14 "strings" 15 ) 16 17 const PackagesFileName = "packages.config" 18 19 // Register packages.config extractor 20 func init() { 21 register(&packagesExtractor{}) 22 } 23 24 // packages.config dependency extractor 25 type packagesExtractor struct { 26 allDependencies map[string]*buildinfo.Dependency 27 childrenMap map[string][]string 28 rootDependencies []string 29 } 30 31 func (extractor *packagesExtractor) IsCompatible(projectName, dependenciesSource string) bool { 32 if strings.HasSuffix(dependenciesSource, PackagesFileName) { 33 log.Debug("Found", dependenciesSource, "file for project:", projectName) 34 return true 35 } 36 return false 37 } 38 39 func (extractor *packagesExtractor) DirectDependencies() ([]string, error) { 40 return getDirectDependencies(extractor.allDependencies, extractor.childrenMap), nil 41 } 42 43 func (extractor *packagesExtractor) AllDependencies() (map[string]*buildinfo.Dependency, error) { 44 return extractor.allDependencies, nil 45 } 46 47 func (extractor *packagesExtractor) ChildrenMap() (map[string][]string, error) { 48 return extractor.childrenMap, nil 49 } 50 51 // Create new packages.config extractor 52 func (extractor *packagesExtractor) new(dependenciesSource string) (Extractor, error) { 53 newExtractor := &packagesExtractor{allDependencies: map[string]*buildinfo.Dependency{}, childrenMap: map[string][]string{}} 54 packagesConfig, err := newExtractor.loadPackagesConfig(dependenciesSource) 55 if err != nil { 56 return nil, err 57 } 58 59 globalPackagesCache, err := newExtractor.getGlobalPackagesCache() 60 if err != nil { 61 return nil, err 62 } 63 64 err = newExtractor.extract(packagesConfig, globalPackagesCache) 65 return newExtractor, err 66 } 67 68 func (extractor *packagesExtractor) extract(packagesConfig *packagesConfig, globalPackagesCache string) error { 69 for _, nuget := range packagesConfig.XmlPackages { 70 id := strings.ToLower(nuget.Id) 71 nPackage := &nugetPackage{id: id, version: nuget.Version, dependencies: map[string]bool{}} 72 // First lets check if the original version exists within the file system: 73 pack, err := createNugetPackage(globalPackagesCache, nuget, nPackage) 74 if err != nil { 75 return err 76 } 77 if pack == nil { 78 // If doesn't exists lets build the array of alternative versions. 79 alternativeVersions := createAlternativeVersionForms(nuget.Version) 80 // Now lets do a loop to run over the alternative possibilities 81 for i := 0; i < len(alternativeVersions); i++ { 82 nPackage.version = alternativeVersions[i] 83 pack, err = createNugetPackage(globalPackagesCache, nuget, nPackage) 84 if err != nil { 85 return err 86 } 87 if pack != nil { 88 break 89 } 90 } 91 } 92 if pack != nil { 93 extractor.allDependencies[id] = pack.dependency 94 extractor.childrenMap[id] = pack.getDependencies() 95 } else { 96 log.Warn(fmt.Sprintf("The following NuGet package %s with version %s was not found in the NuGet cache %s."+absentNupkgWarnMsg, nuget.Id, nuget.Version, globalPackagesCache)) 97 } 98 } 99 return nil 100 } 101 102 // NuGet allows the version will be with missing or unnecessary zeros 103 // This method will return a list of the possible alternative versions 104 // "1.0" --> []string{"1.0.0.0", "1.0.0", "1"} 105 // "1" --> []string{"1.0.0.0", "1.0.0", "1.0"} 106 // "1.2" --> []string{"1.2.0.0", "1.2.0"} 107 // "1.22.33" --> []string{"1.22.33.0"} 108 // "1.22.33.44" --> []string{} 109 // "1.0.2" --> []string{"1.0.2.0"} 110 func createAlternativeVersionForms(originalVersion string) []string { 111 versionSlice := strings.Split(originalVersion, ".") 112 versionSliceSize := len(versionSlice) 113 for i := 4; i > versionSliceSize; i-- { 114 versionSlice = append(versionSlice, "0") 115 } 116 117 var alternativeVersions []string 118 119 for i := 4; i > 0; i-- { 120 version := strings.Join(versionSlice[:i], ".") 121 if version != originalVersion { 122 alternativeVersions = append(alternativeVersions, version) 123 } 124 if !strings.HasSuffix(version, ".0") { 125 return alternativeVersions 126 } 127 } 128 return alternativeVersions 129 } 130 131 func (extractor *packagesExtractor) loadPackagesConfig(dependenciesSource string) (*packagesConfig, error) { 132 content, err := os.ReadFile(dependenciesSource) 133 if err != nil { 134 return nil, err 135 } 136 137 config := &packagesConfig{} 138 err = xml.Unmarshal(content, config) 139 if err != nil { 140 return nil, errorutils.CheckError(err) 141 } 142 return config, nil 143 } 144 145 type dfsHelper struct { 146 visited bool 147 notRoot bool 148 circular bool 149 } 150 151 func getDirectDependencies(allDependencies map[string]*buildinfo.Dependency, childrenMap map[string][]string) []string { 152 helper := map[string]*dfsHelper{} 153 for id := range allDependencies { 154 helper[id] = &dfsHelper{} 155 } 156 157 for id := range allDependencies { 158 if helper[id].visited { 159 continue 160 } 161 searchRootDependencies(helper, id, allDependencies, childrenMap, map[string]bool{id: true}) 162 } 163 var rootDependencies []string 164 for id, nodeData := range helper { 165 if !nodeData.notRoot || nodeData.circular { 166 rootDependencies = append(rootDependencies, id) 167 } 168 } 169 170 return rootDependencies 171 } 172 173 func searchRootDependencies(dfsHelper map[string]*dfsHelper, currentId string, allDependencies map[string]*buildinfo.Dependency, childrenMap map[string][]string, traversePath map[string]bool) { 174 if dfsHelper[currentId].visited { 175 return 176 } 177 for _, next := range childrenMap[currentId] { 178 if _, ok := allDependencies[next]; !ok { 179 // No such dependency 180 continue 181 } 182 if traversePath[next] { 183 for circular := range traversePath { 184 dfsHelper[circular].circular = true 185 } 186 continue 187 } 188 189 // Not root dependency 190 dfsHelper[next].notRoot = true 191 traversePath[next] = true 192 searchRootDependencies(dfsHelper, next, allDependencies, childrenMap, traversePath) 193 delete(traversePath, next) 194 } 195 dfsHelper[currentId].visited = true 196 } 197 198 func createNugetPackage(packagesPath string, nuget xmlPackage, nPackage *nugetPackage) (*nugetPackage, error) { 199 nupkgPath := filepath.Join(packagesPath, nPackage.id, nPackage.version, strings.Join([]string{nPackage.id, nPackage.version, "nupkg"}, ".")) 200 201 exists, err := fileutils.IsFileExists(nupkgPath, false) 202 203 if err != nil { 204 return nil, err 205 } 206 207 if !exists { 208 return nil, nil 209 } 210 211 fileDetails, err := fileutils.GetFileDetails(nupkgPath) 212 if err != nil { 213 return nil, err 214 } 215 nPackage.dependency = &buildinfo.Dependency{Id: nuget.Id + ":" + nuget.Version, Checksum: buildinfo.Checksum{Sha1: fileDetails.Checksum.Sha1, Md5: fileDetails.Checksum.Md5}} 216 217 // Nuspec file that holds the metadata for the package. 218 nuspecPath := filepath.Join(packagesPath, nPackage.id, nPackage.version, strings.Join([]string{nPackage.id, "nuspec"}, ".")) 219 nuspecContent, err := os.ReadFile(nuspecPath) 220 if err != nil { 221 return nil, errorutils.CheckError(err) 222 } 223 224 nuspec := &nuspec{} 225 err = xml.Unmarshal(nuspecContent, nuspec) 226 if err != nil { 227 pack := nPackage.id + ":" + nPackage.version 228 log.Warn("Package:", pack, "couldn't be parsed due to:", err.Error(), ". Skipping the package dependency.") 229 return nPackage, nil 230 } 231 232 for _, dependency := range nuspec.Metadata.Dependencies.Dependencies { 233 nPackage.dependencies[strings.ToLower(dependency.Id)] = true 234 } 235 236 for _, group := range nuspec.Metadata.Dependencies.Groups { 237 for _, dependency := range group.Dependencies { 238 nPackage.dependencies[strings.ToLower(dependency.Id)] = true 239 } 240 } 241 242 return nPackage, nil 243 } 244 245 type nugetPackage struct { 246 id string 247 version string 248 dependency *buildinfo.Dependency 249 dependencies map[string]bool // Set of dependencies 250 } 251 252 func (nugetPackage *nugetPackage) getDependencies() []string { 253 var dependencies []string 254 for key := range nugetPackage.dependencies { 255 dependencies = append(dependencies, key) 256 } 257 258 return dependencies 259 } 260 261 func (extractor *packagesExtractor) getGlobalPackagesCache() (string, error) { 262 localsCmd, err := dotnet.NewToolchainCmd(dotnet.Nuget) 263 if err != nil { 264 return "", err 265 } 266 //nuget locals global-packages -list 267 localsCmd.Command = append(localsCmd.Command, []string{"locals", "global-packages"}...) 268 localsCmd.CommandFlags = []string{"-list"} 269 270 output, err := gofrogcmd.RunCmdOutput(localsCmd) 271 if err != nil { 272 return "", err 273 } 274 275 globalPackagesPath := strings.TrimSpace(strings.TrimPrefix(output, "global-packages:")) 276 exists, err := fileutils.IsDirExists(globalPackagesPath, false) 277 if err != nil { 278 return "", err 279 } 280 if !exists { 281 return "", errorutils.CheckError(fmt.Errorf("Could not find global packages path at: %s", globalPackagesPath)) 282 } 283 return globalPackagesPath, nil 284 } 285 286 // packages.config xml objects for unmarshalling 287 type packagesConfig struct { 288 XMLName xml.Name `xml:"packages"` 289 XmlPackages []xmlPackage `xml:"package"` 290 } 291 292 type xmlPackage struct { 293 Id string `xml:"id,attr"` 294 Version string `xml:"version,attr"` 295 } 296 297 type nuspec struct { 298 XMLName xml.Name `xml:"package"` 299 Metadata metadata `xml:"metadata"` 300 } 301 302 type metadata struct { 303 Dependencies xmlDependencies `xml:"dependencies"` 304 } 305 306 type xmlDependencies struct { 307 Groups []group `xml:"group"` 308 Dependencies []xmlPackage `xml:"dependency"` 309 } 310 311 type group struct { 312 TargetFramework string `xml:"targetFramework,attr"` 313 Dependencies []xmlPackage `xml:"dependency"` 314 }