github.com/jfrog/jfrog-cli-core@v1.12.1/artifactory/utils/dotnet/solution/solution.go (about) 1 package solution 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "os" 8 "path/filepath" 9 "regexp" 10 "strings" 11 12 "github.com/jfrog/jfrog-cli-core/artifactory/utils/dotnet/dependencies" 13 "github.com/jfrog/jfrog-cli-core/artifactory/utils/dotnet/solution/project" 14 "github.com/jfrog/jfrog-cli-core/utils/ioutils" 15 "github.com/jfrog/jfrog-client-go/artifactory/buildinfo" 16 "github.com/jfrog/jfrog-client-go/utils" 17 "github.com/jfrog/jfrog-client-go/utils/errorutils" 18 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 19 "github.com/jfrog/jfrog-client-go/utils/log" 20 ) 21 22 type Solution interface { 23 BuildInfo(module string) (*buildinfo.BuildInfo, error) 24 Marshal() ([]byte, error) 25 GetProjects() []project.Project 26 } 27 28 var projectRegExp *regexp.Regexp 29 30 func Load(solutionPath, slnFile string) (Solution, error) { 31 solution := &solution{path: solutionPath, slnFile: slnFile} 32 err := solution.getDependenciesSources() 33 if err != nil { 34 return solution, err 35 } 36 err = solution.loadProjects() 37 return solution, err 38 } 39 40 type solution struct { 41 path string 42 // If there are more then one sln files in the directory, 43 // the user must specify as arguments the sln file that should be used. 44 slnFile string 45 projects []project.Project 46 dependenciesSources []string 47 } 48 49 func (solution *solution) BuildInfo(module string) (*buildinfo.BuildInfo, error) { 50 build := &buildinfo.BuildInfo{} 51 var modules []buildinfo.Module 52 for _, project := range solution.projects { 53 // Get All project dependencies 54 dependencies, err := project.Extractor().AllDependencies() 55 if err != nil { 56 return nil, err 57 } 58 var projectDependencies []buildinfo.Dependency 59 60 for _, dep := range dependencies { 61 projectDependencies = append(projectDependencies, *dep) 62 } 63 module := buildinfo.Module{Id: getModuleId(module, project.Name()), Type: buildinfo.Nuget, Dependencies: projectDependencies} 64 modules = append(modules, module) 65 } 66 build.Modules = modules 67 return build, nil 68 } 69 70 func getModuleId(customModuleID, projectName string) string { 71 if customModuleID != "" { 72 return customModuleID 73 } 74 return projectName 75 } 76 77 func (solution *solution) Marshal() ([]byte, error) { 78 return json.Marshal(&struct { 79 Projects []project.Project `json:"projects,omitempty"` 80 }{ 81 Projects: solution.projects, 82 }) 83 } 84 85 func (solution *solution) GetProjects() []project.Project { 86 return solution.projects 87 } 88 89 func (solution *solution) loadProjects() error { 90 slnProjects, err := solution.getProjectsFromSlns() 91 if err != nil { 92 return err 93 } 94 if slnProjects != nil { 95 return solution.loadProjectsFromSolutionFile(slnProjects) 96 } 97 98 return solution.loadSingleProjectFromDir() 99 } 100 101 func (solution *solution) loadProjectsFromSolutionFile(slnProjects []string) error { 102 for _, projectLine := range slnProjects { 103 projectName, projFilePath, err := parseProjectLine(projectLine, solution.path) 104 if err != nil { 105 log.Error(err) 106 continue 107 } 108 // Looking for .*proj files. 109 if !strings.HasSuffix(filepath.Ext(projFilePath), "proj") { 110 log.Debug(fmt.Sprintf("Skipping a project \"%s\", since it doesn't have a '.*proj' file path.", projectName)) 111 continue 112 } 113 solution.loadSingleProject(projectName, projFilePath) 114 } 115 return nil 116 } 117 118 func (solution *solution) loadSingleProjectFromDir() error { 119 // List files with .*proj extension. 120 projFiles, err := fileutils.ListFilesByFilterFunc(solution.path, func(filePath string) (bool, error) { 121 return strings.HasSuffix(filepath.Ext(filePath), "proj"), nil 122 }) 123 if err != nil { 124 return err 125 } 126 127 if len(projFiles) == 1 { 128 projectName := strings.TrimSuffix(filepath.Base(projFiles[0]), filepath.Ext(projFiles[0])) 129 solution.loadSingleProject(projectName, projFiles[0]) 130 } 131 return nil 132 } 133 134 func (solution *solution) loadSingleProject(projectName, projFilePath string) { 135 // First we wil find the project's dependencies source. 136 // It can be located directly in the project's root directory or in a directory with the project name under the solution root 137 // or under obj directory (in case of assets.json file) 138 projectRootPath := filepath.Dir(projFilePath) 139 projectPathPattern := filepath.Join(projectRootPath, dependencies.AssetDirName) + string(filepath.Separator) 140 projectNamePattern := string(filepath.Separator) + projectName + string(filepath.Separator) 141 var dependenciesSource string 142 for _, source := range solution.dependenciesSources { 143 if projectRootPath == filepath.Dir(source) || strings.Contains(source, projectPathPattern) || strings.Contains(source, projectNamePattern) { 144 dependenciesSource = source 145 break 146 } 147 } 148 // If no dependencies source was found, we will skip the current project 149 if len(dependenciesSource) == 0 { 150 log.Debug(fmt.Sprintf("Project dependencies was not found for project: %s", projectName)) 151 return 152 } 153 proj, err := project.Load(projectName, projectRootPath, dependenciesSource) 154 if err != nil { 155 log.Error(err) 156 return 157 } 158 if proj.Extractor() != nil { 159 solution.projects = append(solution.projects, proj) 160 } 161 return 162 } 163 164 // Finds all the projects by reading the content of the sln files. 165 // Returns a slice with all the projects in the solution. 166 func (solution *solution) getProjectsFromSlns() ([]string, error) { 167 var allProjects []string 168 slnFiles, err := solution.getSlnFiles() 169 if err != nil { 170 return nil, err 171 } 172 for _, slnFile := range slnFiles { 173 projects, err := parseSlnFile(slnFile) 174 if err != nil { 175 return nil, err 176 } 177 allProjects = append(allProjects, projects...) 178 } 179 return allProjects, nil 180 } 181 182 // If sln file is not provided, finds all sln files in the directory. 183 func (solution *solution) getSlnFiles() (slnFiles []string, err error) { 184 if solution.slnFile != "" { 185 slnFiles = append(slnFiles, filepath.Join(solution.path, solution.slnFile)) 186 } else { 187 slnFiles, err = fileutils.ListFilesByFilterFunc(solution.path, func(filePath string) (bool, error) { 188 return filepath.Ext(filePath) == ".sln", nil 189 }) 190 } 191 return 192 } 193 194 // Parses the project line for the project name and path information. 195 // Returns the name and path to proj file 196 func parseProjectLine(projectLine, path string) (projectName, projFilePath string, err error) { 197 parsedLine := strings.Split(projectLine, "=") 198 if len(parsedLine) <= 1 { 199 return "", "", errors.New("Unexpected project line format: " + projectLine) 200 } 201 202 projectInfo := strings.Split(parsedLine[1], ",") 203 if len(projectInfo) <= 2 { 204 return "", "", errors.New("Unexpected project information format: " + parsedLine[1]) 205 } 206 projectName = removeQuotes(projectInfo[0]) 207 // In case we are running on a non-Windows OS, the solution root path and the relative path to proj file might used different path separators. 208 // We want to make sure we will get a valid path after we join both parts, so we will replace the proj separators. 209 if utils.IsWindows() { 210 projectInfo[1] = ioutils.UnixToWinPathSeparator(projectInfo[1]) 211 } else { 212 projectInfo[1] = ioutils.WinToUnixPathSeparator(projectInfo[1]) 213 } 214 projFilePath = filepath.Join(path, filepath.FromSlash(removeQuotes(projectInfo[1]))) 215 return 216 } 217 218 // Parse the sln file according to project regular expression and returns all the founded lines by the regex 219 func parseSlnFile(slnFile string) ([]string, error) { 220 var err error 221 if projectRegExp == nil { 222 projectRegExp, err = utils.GetRegExp(`Project\("(.*)\nEndProject`) 223 if err != nil { 224 return nil, err 225 } 226 } 227 228 content, err := os.ReadFile(slnFile) 229 if err != nil { 230 return nil, err 231 } 232 projects := projectRegExp.FindAllString(string(content), -1) 233 return projects, nil 234 } 235 236 func removeQuotes(value string) string { 237 return strings.Trim(strings.TrimSpace(value), "\"") 238 } 239 240 // We'll walk through the file system to find all potential dependencies sources: packages.config and project.assets.json files 241 func (solution *solution) getDependenciesSources() error { 242 err := fileutils.Walk(solution.path, func(path string, f os.FileInfo, err error) error { 243 if strings.HasSuffix(path, dependencies.PackagesFileName) || strings.HasSuffix(path, dependencies.AssetFileName) { 244 absPath, err := filepath.Abs(path) 245 if err != nil { 246 return err 247 } 248 solution.dependenciesSources = append(solution.dependenciesSources, absPath) 249 } 250 return nil 251 }, true) 252 253 return errorutils.CheckError(err) 254 }