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  }