github.com/bilus/oya@v0.0.3-0.20190301162104-da4acbd394c6/pkg/project/packs.go (about)

     1  package project
     2  
     3  import (
     4  	"sort"
     5  
     6  	"github.com/bilus/oya/pkg/deptree"
     7  	"github.com/bilus/oya/pkg/oyafile"
     8  	"github.com/bilus/oya/pkg/pack"
     9  	"github.com/bilus/oya/pkg/repo"
    10  	"github.com/bilus/oya/pkg/types"
    11  )
    12  
    13  func (p *Project) Require(pack pack.Pack) error {
    14  	raw, err := p.rawOyafileIn(p.RootDir)
    15  	if err != nil {
    16  		return err
    17  	}
    18  	err = raw.AddRequire(pack)
    19  	if err != nil {
    20  		return err
    21  	}
    22  
    23  	p.dependencies = nil // Force reload.
    24  	return nil
    25  }
    26  
    27  func (p *Project) Install(pack pack.Pack) error {
    28  	return pack.Install(p.installDir)
    29  }
    30  
    31  func (p *Project) IsInstalled(pack pack.Pack) (bool, error) {
    32  	return pack.IsInstalled(p.installDir)
    33  }
    34  
    35  // InstallPacks installs packs used by the project.
    36  // It works in two steps:
    37  // 1. It goes through all Import: directives and updates the Require: section with missing packs in their latest versions.
    38  // 2. It installs all packs that haven't been installed.
    39  func (p *Project) InstallPacks() error {
    40  	err := p.updateDependencies()
    41  	if err != nil {
    42  		return err
    43  	}
    44  
    45  	deps, err := p.Deps()
    46  	if err != nil {
    47  		return err
    48  	}
    49  	return deps.ForEach(
    50  		func(pack pack.Pack) error {
    51  			_, ok := pack.ReplacementPath()
    52  			if ok {
    53  				return nil
    54  			}
    55  			installed, err := p.IsInstalled(pack)
    56  			if err != nil {
    57  				return err
    58  			}
    59  			if installed {
    60  				return nil
    61  			}
    62  			err = p.Install(pack)
    63  			return err
    64  		},
    65  	)
    66  }
    67  
    68  func (p *Project) FindRequiredPack(importPath types.ImportPath) (pack.Pack, bool, error) {
    69  	deps, err := p.Deps()
    70  	if err != nil {
    71  		return pack.Pack{}, false, err
    72  	}
    73  	return deps.Find(importPath)
    74  }
    75  
    76  func (p *Project) Deps() (Deps, error) {
    77  	if p.dependencies != nil {
    78  		return p.dependencies, nil
    79  	}
    80  
    81  	o, err := p.oyafileIn(p.RootDir)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	installDirs := []string{
    86  		p.installDir,
    87  	}
    88  	requires, err := resolvePackReferences(o.Requires)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	ldr, err := deptree.New(p.RootDir, installDirs, requires)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	err = ldr.Explode()
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	p.dependencies = ldr
   101  	return ldr, nil
   102  }
   103  
   104  func (p *Project) updateDependencies() error {
   105  	files, err := p.List(p.RootDir)
   106  	if err != nil {
   107  		return err
   108  	}
   109  
   110  	deps, err := p.Deps()
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	// Collect all import paths from all Oyafiles. Make them unique.
   116  	// Also, sort them to make writing reliable tests easier.
   117  	importPaths := uniqueSortedImportPaths(files)
   118  	for _, importPath := range importPaths {
   119  		_, found, err := deps.Find(importPath)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		if found {
   124  			continue
   125  		}
   126  
   127  		l, err := repo.Open(importPath)
   128  		if err != nil {
   129  			// Import paths can also be relative to the root directory.
   130  			// BUG(bilus): I don't particularly like it how tihs logic is split. Plus we may be masking some other errors this way
   131  			if _, ok := err.(repo.ErrNotGithub); ok {
   132  				continue
   133  			}
   134  			return err
   135  		}
   136  
   137  		pack, err := l.LatestVersion()
   138  		if err != nil {
   139  			return err
   140  		}
   141  		err = p.Require(pack)
   142  		if err != nil {
   143  			return err
   144  		}
   145  	}
   146  	return nil
   147  }
   148  
   149  func uniqueSortedImportPaths(oyafiles []*oyafile.Oyafile) []types.ImportPath {
   150  	importPathSet := make(map[types.ImportPath]struct{})
   151  	importPaths := make([]types.ImportPath, 0)
   152  	for _, o := range oyafiles {
   153  		for _, importPath := range o.Imports {
   154  			if _, exists := importPathSet[importPath]; !exists {
   155  				importPaths = append(importPaths, importPath)
   156  			}
   157  			importPathSet[importPath] = struct{}{}
   158  		}
   159  	}
   160  
   161  	sort.Slice(importPaths, func(i, j int) bool {
   162  		return importPaths[i] < importPaths[j]
   163  	})
   164  
   165  	return importPaths
   166  }
   167  
   168  func resolvePackReferences(references []oyafile.PackReference) ([]pack.Pack, error) {
   169  	packs := make([]pack.Pack, len(references))
   170  	for i, reference := range references {
   171  		l, err := repo.Open(reference.ImportPath)
   172  		if err != nil {
   173  			return nil, err
   174  		}
   175  		pack, err := l.Version(reference.Version)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  		if len(reference.ReplacementPath) > 0 {
   180  			pack = pack.LocalReplacement(reference.ReplacementPath)
   181  		}
   182  		packs[i] = pack
   183  	}
   184  	return packs, nil
   185  }