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

     1  package debian
     2  
     3  import (
     4  	"path/filepath"
     5  	"sync"
     6  
     7  	"github.com/apex/log"
     8  
     9  	"github.com/fossas/fossa-cli/api/fossa"
    10  	"github.com/fossas/fossa-cli/graph"
    11  	"github.com/fossas/fossa-cli/pkg"
    12  )
    13  
    14  type Cmd struct {
    15  	Directory string
    16  	Upload    bool
    17  	DebCmd    func(...string) (string, error)
    18  }
    19  
    20  // New returns a new Cmd that implements "apt-cache" to find dependency information.
    21  func New() Cmd {
    22  	return Cmd{
    23  		Directory: "/usr/share/doc/",
    24  		Upload:    true,
    25  		DebCmd:    aptCache,
    26  	}
    27  }
    28  
    29  // Dependencies returns a dependency graph by analyzing a debian package.Dependencies.
    30  // Notes:
    31  // 	(A "valid dependency" is one that has a valid path in "d.Directory+target"). Many types of
    32  //	packages are found when running d.DebCmd and it is important to determine which ones
    33  //	are used by the system. This is the reason we debug errors when a tarball upload fails.
    34  //
    35  // Analysis steps are as follows:
    36  //	1. Find all transitive dependencies for the target package.
    37  //	2. Upload all valid dependencies and retrieve locator information.
    38  //	3. Loop through all valid dependencies and find their direct dependencies using d.DebCmd
    39  //	4. Construct a dependency graph with the direct dependency information.Dependencies
    40  //
    41  func (d Cmd) Dependencies(target string) (graph.Deps, error) {
    42  	// Get all relevant dependencies.
    43  	depList, err := transitiveDeps(d.DebCmd, target)
    44  	if err != nil {
    45  		return graph.Deps{}, err
    46  	}
    47  
    48  	// Loop over the dep list create the locator map from valid package uploads.
    49  	locatorMap := uploadDeps(depList, d.Directory, d.Upload)
    50  
    51  	// Loop over the locator map, get direct deps for each and build the graph.
    52  	depGraph := dependencyGraph(d.DebCmd, locatorMap)
    53  
    54  	directDeps := []pkg.Import{
    55  		pkg.Import{
    56  			Target: target,
    57  			Resolved: pkg.ID{
    58  				Type:     pkg.Raw,
    59  				Name:     target,
    60  				Revision: locatorMap[target],
    61  			},
    62  		},
    63  	}
    64  
    65  	return graph.Deps{
    66  		Direct:     directDeps,
    67  		Transitive: depGraph,
    68  	}, nil
    69  }
    70  
    71  func uploadDeps(dependencies map[string]string, directory string, upload bool) map[string]string {
    72  	var locatorMap = make(map[string]string)
    73  	wg := sync.WaitGroup{}
    74  	mapLock := sync.RWMutex{}
    75  
    76  	for d := range dependencies {
    77  		wg.Add(1)
    78  		go func(dep string) {
    79  			defer wg.Done()
    80  
    81  			revision, err := fossa.UploadTarballDependency(filepath.Join(directory, dep), upload, true)
    82  			if err != nil {
    83  				log.Debugf("Error uploading %v: %+v", dep, err)
    84  			} else {
    85  				mapLock.Lock()
    86  				locatorMap[dep] = revision.Revision
    87  				mapLock.Unlock()
    88  			}
    89  		}(d)
    90  	}
    91  	wg.Wait()
    92  
    93  	return locatorMap
    94  }
    95  
    96  func dependencyGraph(command func(...string) (string, error), locatorMap map[string]string) map[pkg.ID]pkg.Package {
    97  	wg := sync.WaitGroup{}
    98  	mapLock := sync.RWMutex{}
    99  	var depGraph = make(map[pkg.ID]pkg.Package)
   100  	for t, r := range locatorMap {
   101  		wg.Add(1)
   102  		go func(target, revision string) {
   103  			defer wg.Done()
   104  			if _, ok := locatorMap[target]; ok {
   105  				dependencies, err := directDeps(command, target)
   106  				if err != nil {
   107  					log.Debugf("Error retrieving deps for %+v: %+v", target, err)
   108  				}
   109  
   110  				importedDeps := []pkg.Import{}
   111  				for _, dep := range dependencies {
   112  					if _, ok := locatorMap[dep]; ok {
   113  						importedDeps = append(importedDeps, pkg.Import{
   114  							Target: dep,
   115  							Resolved: pkg.ID{
   116  								Type:     pkg.Raw,
   117  								Name:     dep,
   118  								Revision: locatorMap[dep],
   119  							},
   120  						})
   121  					}
   122  				}
   123  
   124  				targetID := pkg.ID{
   125  					Type:     pkg.Raw,
   126  					Name:     target,
   127  					Revision: revision,
   128  				}
   129  				mapLock.Lock()
   130  				depGraph[targetID] = pkg.Package{
   131  					ID:      targetID,
   132  					Imports: importedDeps,
   133  				}
   134  				mapLock.Unlock()
   135  			}
   136  		}(t, r)
   137  	}
   138  	wg.Wait()
   139  
   140  	return depGraph
   141  }