github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/buildtools/maven/pom.go (about)

     1  package maven
     2  
     3  import (
     4  	"encoding/xml"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/pkg/errors"
    10  
    11  	"github.com/fossas/fossa-cli/files"
    12  	"github.com/fossas/fossa-cli/graph"
    13  	"github.com/fossas/fossa-cli/pkg"
    14  )
    15  
    16  // A Manifest represents a POM manifest file.
    17  type Manifest struct {
    18  	Project              xml.Name     `xml:"project"`
    19  	Parent               Parent       `xml:"parent"`
    20  	Modules              []string     `xml:"modules>module"`
    21  	ArtifactID           string       `xml:"artifactId"`
    22  	GroupID              string       `xml:"groupId"`
    23  	Version              string       `xml:"version"`
    24  	Description          string       `xml:"description"`
    25  	Name                 string       `xml:"name"`
    26  	URL                  string       `xml:"url"`
    27  	Dependencies         []Dependency `xml:"dependencies>dependency"`
    28  	DependencyManagement []Dependency `xml:"dependencyManagement>dependencies>dependency"`
    29  }
    30  
    31  type Parent struct {
    32  	ArtifactID string `xml:"artifactId"`
    33  	GroupID    string `xml:"groupId"`
    34  	Version    string `xml:"version"`
    35  }
    36  
    37  type Dependency struct {
    38  	GroupId    string `xml:"groupId"`
    39  	ArtifactId string `xml:"artifactId"`
    40  	Version    string `xml:"version"`
    41  
    42  	// Scope is where the dependency is used, such as "test" or "runtime".
    43  	Scope string `xml:"scope"`
    44  
    45  	Failed bool
    46  }
    47  
    48  // ID returns the dependency identifier as groupId:artifactId.
    49  func (d Dependency) ID() string {
    50  	return d.GroupId + ":" + d.ArtifactId
    51  }
    52  
    53  // PomFileGraph returns simply the list of dependencies listed within the manifest file.
    54  func PomFileGraph(target, dir string) (graph.Deps, error) {
    55  	pom, err := ResolveManifestFromTarget(target, dir)
    56  	if err != nil {
    57  		return graph.Deps{}, err
    58  	}
    59  
    60  	// Aggregate `dependencies` and `dependencyManagement` fields.
    61  	dependencyList := combineDependencies(pom.Dependencies, pom.DependencyManagement)
    62  
    63  	deps := graph.Deps{
    64  		Direct:     depsListToImports(dependencyList),
    65  		Transitive: make(map[pkg.ID]pkg.Package),
    66  	}
    67  
    68  	// From just a POM file we don't know what really depends on what, so list all imports in the graph.
    69  	for _, dep := range dependencyList {
    70  		pack := pkg.Package{
    71  			ID: pkg.ID{
    72  				Type:     pkg.Maven,
    73  				Name:     dep.ID(),
    74  				Revision: dep.Version,
    75  			},
    76  		}
    77  		deps.Transitive[pack.ID] = pack
    78  	}
    79  
    80  	return deps, nil
    81  }
    82  
    83  // ResolveManifestFromTarget tries to determine what target is supposed to be and then reads the POM
    84  // manifest file pointed to by target if it is a path to such a file or module.
    85  func ResolveManifestFromTarget(target, dir string) (*Manifest, error) {
    86  	pomFile := filepath.Join(dir, target)
    87  	stat, err := os.Stat(pomFile)
    88  	if err != nil {
    89  		// target is not a path.
    90  		if strings.Count(target, ":") == 1 {
    91  			// This is likely a module ID.
    92  			return nil, errors.Errorf("cannot identify POM file for module %q", target)
    93  		}
    94  		return nil, errors.Wrapf(err, "manifest file for %q cannot be read", target)
    95  	}
    96  	if stat.IsDir() {
    97  		// We have the directory and will assume it uses the standard name for the manifest file.
    98  		pomFile = filepath.Join(target, "pom.xml")
    99  	}
   100  
   101  	var pom Manifest
   102  	if err := files.ReadXML(&pom, pomFile); err != nil {
   103  		return nil, err
   104  	}
   105  	return &pom, nil
   106  }
   107  
   108  // combineDependencies combines and dedupes two lists of Dependencies.
   109  func combineDependencies(listOne, listTwo []Dependency) []Dependency {
   110  	mergedList := listOne
   111  	listOneMap := make(map[Dependency]bool)
   112  	for _, dep := range listOne {
   113  		listOneMap[dep] = true
   114  	}
   115  	for _, dep := range listTwo {
   116  		if _, exists := listOneMap[dep]; !exists {
   117  			mergedList = append(mergedList, dep)
   118  		}
   119  	}
   120  	return mergedList
   121  }