github.com/tooploox/oya@v0.0.21-0.20230524103240-1cda1861aad6/pkg/project/packs.go (about)

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