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 }