github.com/jfrog/build-info-go@v1.9.26/utils/pythonutils/poetryutils.go (about) 1 package pythonutils 2 3 import ( 4 "errors" 5 "os" 6 "strings" 7 8 "github.com/BurntSushi/toml" 9 "golang.org/x/exp/maps" 10 ) 11 12 type PyprojectToml struct { 13 Tool map[string]PoetryPackage 14 } 15 type PoetryPackage struct { 16 Name string 17 Version string 18 Dependencies map[string]interface{} 19 DevDependencies map[string]interface{} `toml:"dev-dependencies"` 20 } 21 22 type PoetryLock struct { 23 Package []*PoetryPackage 24 } 25 26 // Extract all poetry dependencies from the pyproject.toml and poetry.lock files. 27 // Returns a dependency map of all the installed poetry packages in the current environment and another list of the top level dependencies. 28 func getPoetryDependencies(srcPath string) (graph map[string][]string, directDependencies []string, err error) { 29 filePath, err := getPoetryLockFilePath(srcPath) 30 if err != nil || filePath == "" { 31 // Error was returned or poetry.lock does not exist in directory. 32 return map[string][]string{}, []string{}, err 33 } 34 projectName, directDependencies, err := getPackageNameFromPyproject(srcPath) 35 if err != nil { 36 return map[string][]string{}, []string{}, err 37 } 38 // Extract packages names from poetry.lock 39 dependencies, dependenciesVersions, err := extractPackagesFromPoetryLock(filePath) 40 if err != nil { 41 return map[string][]string{}, []string{}, err 42 } 43 graph = make(map[string][]string) 44 // Add the root node - the project itself. 45 for _, directDependency := range directDependencies { 46 directDependencyName := directDependency + ":" + dependenciesVersions[strings.ToLower(directDependency)] 47 graph[projectName] = append(graph[projectName], directDependencyName) 48 } 49 // Add versions to all dependencies 50 for dependency, transitiveDependencies := range dependencies { 51 for _, transitiveDependency := range transitiveDependencies { 52 transitiveDependencyName := transitiveDependency + ":" + dependenciesVersions[strings.ToLower(transitiveDependency)] 53 graph[dependency] = append(graph[dependency], transitiveDependencyName) 54 } 55 } 56 return graph, graph[projectName], nil 57 } 58 59 func getPackageNameFromPyproject(srcPath string) (string, []string, error) { 60 filePath, err := getPyprojectFilePath(srcPath) 61 if err != nil || filePath == "" { 62 // Error was returned or pyproject.toml does not exist in directory. 63 return "", []string{}, err 64 } 65 // Extract package name from pyproject.toml. 66 project, err := extractProjectFromPyproject(filePath) 67 if err != nil { 68 return "", []string{}, err 69 } 70 return project.Name, append(maps.Keys(project.Dependencies), maps.Keys(project.DevDependencies)...), nil 71 } 72 73 // Look for 'pyproject.toml' file in current work dir. 74 // If found, return its absolute path. 75 func getPyprojectFilePath(srcPath string) (string, error) { 76 return getFilePath(srcPath, "pyproject.toml") 77 } 78 79 // Look for 'poetry.lock' file in current work dir. 80 // If found, return its absolute path. 81 func getPoetryLockFilePath(srcPath string) (string, error) { 82 return getFilePath(srcPath, "poetry.lock") 83 } 84 85 // Get the project-name by parsing the pyproject.toml file. 86 func extractProjectFromPyproject(pyprojectFilePath string) (project PoetryPackage, err error) { 87 content, err := os.ReadFile(pyprojectFilePath) 88 if err != nil { 89 return 90 } 91 var pyprojectFile PyprojectToml 92 _, err = toml.Decode(string(content), &pyprojectFile) 93 if err != nil { 94 return 95 } 96 if poetryProject, ok := pyprojectFile.Tool["poetry"]; ok { 97 // Extract project name from file content. 98 poetryProject.Name = poetryProject.Name + ":" + poetryProject.Version 99 return poetryProject, nil 100 } 101 return PoetryPackage{}, errors.New("Couldn't find project name and version in " + pyprojectFilePath) 102 } 103 104 // Get the project-name by parsing the poetry.lock file 105 func extractPackagesFromPoetryLock(lockFilePath string) (dependencies map[string][]string, dependenciesVersions map[string]string, err error) { 106 content, err := os.ReadFile(lockFilePath) 107 if err != nil { 108 return 109 } 110 var poetryLockFile PoetryLock 111 112 _, err = toml.Decode(string(content), &poetryLockFile) 113 if err != nil { 114 return 115 } 116 dependenciesVersions = make(map[string]string) 117 dependencies = make(map[string][]string) 118 for _, dependency := range poetryLockFile.Package { 119 dependenciesVersions[strings.ToLower(dependency.Name)] = dependency.Version 120 dependencyName := dependency.Name + ":" + dependency.Version 121 dependencies[dependencyName] = maps.Keys(dependency.Dependencies) 122 } 123 return 124 }