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  }