github.com/osievert/jfrog-cli-core@v1.2.7/artifactory/utils/dotnet/solution/solution.go (about) 1 package solution 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "github.com/jfrog/jfrog-cli-core/artifactory/utils/dotnet/dependencies" 8 "github.com/jfrog/jfrog-cli-core/artifactory/utils/dotnet/solution/project" 9 "github.com/jfrog/jfrog-cli-core/utils/ioutils" 10 "github.com/jfrog/jfrog-client-go/artifactory/buildinfo" 11 "github.com/jfrog/jfrog-client-go/utils" 12 "github.com/jfrog/jfrog-client-go/utils/errorutils" 13 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 14 "github.com/jfrog/jfrog-client-go/utils/log" 15 "io/ioutil" 16 "os" 17 "path/filepath" 18 "regexp" 19 "strings" 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 in the project's root directory or in a directory with the project name under the solution root. 137 projectRootPath := filepath.Dir(projFilePath) 138 projectPathPattern := projectRootPath + string(filepath.Separator) 139 projectNamePattern := string(filepath.Separator) + projectName + string(filepath.Separator) 140 var dependenciesSource string 141 for _, source := range solution.dependenciesSources { 142 if strings.Contains(source, projectPathPattern) || strings.Contains(source, projectNamePattern) { 143 dependenciesSource = source 144 break 145 } 146 } 147 // If no dependencies source was found, we will skip the current project 148 if len(dependenciesSource) == 0 { 149 log.Debug(fmt.Sprintf("Project dependencies was not found for project: %s", projectName)) 150 return 151 } 152 proj, err := project.Load(projectName, projectRootPath, dependenciesSource) 153 if err != nil { 154 log.Error(err) 155 return 156 } 157 if proj.Extractor() != nil { 158 solution.projects = append(solution.projects, proj) 159 } 160 return 161 } 162 163 // Finds all the projects by reading the content of the sln files. 164 // Returns a slice with all the projects in the solution. 165 func (solution *solution) getProjectsFromSlns() ([]string, error) { 166 var allProjects []string 167 slnFiles, err := solution.getSlnFiles() 168 if err != nil { 169 return nil, err 170 } 171 for _, slnFile := range slnFiles { 172 projects, err := parseSlnFile(slnFile) 173 if err != nil { 174 return nil, err 175 } 176 allProjects = append(allProjects, projects...) 177 } 178 return allProjects, nil 179 } 180 181 // If sln file is not provided, finds all sln files in the directory. 182 func (solution *solution) getSlnFiles() (slnFiles []string, err error) { 183 if solution.slnFile != "" { 184 slnFiles = append(slnFiles, filepath.Join(solution.path, solution.slnFile)) 185 } else { 186 slnFiles, err = fileutils.ListFilesByFilterFunc(solution.path, func(filePath string) (bool, error) { 187 return filepath.Ext(filePath) == ".sln", nil 188 }) 189 } 190 return 191 } 192 193 // Parses the project line for the project name and path information. 194 // Returns the name and path to proj file 195 func parseProjectLine(projectLine, path string) (projectName, projFilePath string, err error) { 196 parsedLine := strings.Split(projectLine, "=") 197 if len(parsedLine) <= 1 { 198 return "", "", errors.New("Unexpected project line format: " + projectLine) 199 } 200 201 projectInfo := strings.Split(parsedLine[1], ",") 202 if len(projectInfo) <= 2 { 203 return "", "", errors.New("Unexpected project information format: " + parsedLine[1]) 204 } 205 projectName = removeQuotes(projectInfo[0]) 206 // 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. 207 // We want to make sure we will get a valid path after we join both parts, so we will replace the proj separators. 208 if utils.IsWindows() { 209 projectInfo[1] = ioutils.UnixToWinPathSeparator(projectInfo[1]) 210 } else { 211 projectInfo[1] = ioutils.WinToUnixPathSeparator(projectInfo[1]) 212 } 213 projFilePath = filepath.Join(path, filepath.FromSlash(removeQuotes(projectInfo[1]))) 214 return 215 } 216 217 // Parse the sln file according to project regular expression and returns all the founded lines by the regex 218 func parseSlnFile(slnFile string) ([]string, error) { 219 var err error 220 if projectRegExp == nil { 221 projectRegExp, err = utils.GetRegExp(`Project\("(.*)\nEndProject`) 222 if err != nil { 223 return nil, err 224 } 225 } 226 227 content, err := ioutil.ReadFile(slnFile) 228 if err != nil { 229 return nil, err 230 } 231 projects := projectRegExp.FindAllString(string(content), -1) 232 return projects, nil 233 } 234 235 func removeQuotes(value string) string { 236 return strings.Trim(strings.TrimSpace(value), "\"") 237 } 238 239 // We'll walk through the file system to find all potential dependencies sources: packages.config and project.assets.json files 240 func (solution *solution) getDependenciesSources() error { 241 err := fileutils.Walk(solution.path, func(path string, f os.FileInfo, err error) error { 242 if strings.HasSuffix(path, dependencies.PackagesFileName) || strings.HasSuffix(path, dependencies.AssetFileName) { 243 absPath, err := filepath.Abs(path) 244 if err != nil { 245 return err 246 } 247 solution.dependenciesSources = append(solution.dependenciesSources, absPath) 248 } 249 return nil 250 }, true) 251 252 return errorutils.CheckError(err) 253 }