blob: cb95b7607049ecec701cacb35720959e7d0a8c65 [file] [log] [blame]
Bob Badoure6fdd142021-12-09 22:10:43 -08001// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package compliance
16
17import (
Bob Badoure6fdd142021-12-09 22:10:43 -080018 "crypto/md5"
19 "fmt"
20 "io"
21 "io/fs"
Bob Badour5028abc2022-02-09 11:56:58 -080022 "net/url"
Bob Badoure6fdd142021-12-09 22:10:43 -080023 "path/filepath"
24 "regexp"
25 "sort"
26 "strings"
Bob Badoure6fdd142021-12-09 22:10:43 -080027
Bob Badourab5cfbd2022-10-17 17:40:04 -070028 "android/soong/tools/compliance/projectmetadata"
Bob Badoure6fdd142021-12-09 22:10:43 -080029)
30
31var (
Bob Badoure6fdd142021-12-09 22:10:43 -080032 licensesPathRegexp = regexp.MustCompile(`licen[cs]es?/`)
33)
34
35// NoticeIndex transforms license metadata into license text hashes, library
36// names, and install paths indexing them for fast lookup/iteration.
37type NoticeIndex struct {
38 // lg identifies the license graph to which the index applies.
39 lg *LicenseGraph
Bob Badourab5cfbd2022-10-17 17:40:04 -070040 // pmix indexes project metadata
41 pmix *projectmetadata.Index
Bob Badoure6fdd142021-12-09 22:10:43 -080042 // rs identifies the set of resolutions upon which the index is based.
43 rs ResolutionSet
44 // shipped identifies the set of target nodes shipped directly or as derivative works.
45 shipped *TargetNodeSet
46 // rootFS locates the root of the file system from which to read the files.
47 rootFS fs.FS
48 // hash maps license text filenames to content hashes
49 hash map[string]hash
50 // text maps content hashes to content
51 text map[hash][]byte
52 // hashLibInstall maps hashes to libraries to install paths.
53 hashLibInstall map[hash]map[string]map[string]struct{}
Bob Badour6ea14572022-01-23 17:15:46 -080054 // installHashLib maps install paths to libraries to hashes.
55 installHashLib map[string]map[hash]map[string]struct{}
Bob Badoure6fdd142021-12-09 22:10:43 -080056 // libHash maps libraries to hashes.
57 libHash map[string]map[hash]struct{}
58 // targetHash maps target nodes to hashes.
59 targetHashes map[*TargetNode]map[hash]struct{}
60 // projectName maps project directory names to project name text.
61 projectName map[string]string
Colin Crossbb45f8c2022-01-28 15:18:19 -080062 // files lists all the files accessed during indexing
63 files []string
Bob Badoure6fdd142021-12-09 22:10:43 -080064}
65
66// IndexLicenseTexts creates a hashed index of license texts for `lg` and `rs`
67// using the files rooted at `rootFS`.
68func IndexLicenseTexts(rootFS fs.FS, lg *LicenseGraph, rs ResolutionSet) (*NoticeIndex, error) {
69 if rs == nil {
70 rs = ResolveNotices(lg)
71 }
72 ni := &NoticeIndex{
Colin Crossbb45f8c2022-01-28 15:18:19 -080073 lg: lg,
Bob Badourab5cfbd2022-10-17 17:40:04 -070074 pmix: projectmetadata.NewIndex(rootFS),
Colin Crossbb45f8c2022-01-28 15:18:19 -080075 rs: rs,
76 shipped: ShippedNodes(lg),
77 rootFS: rootFS,
78 hash: make(map[string]hash),
79 text: make(map[hash][]byte),
80 hashLibInstall: make(map[hash]map[string]map[string]struct{}),
81 installHashLib: make(map[string]map[hash]map[string]struct{}),
82 libHash: make(map[string]map[hash]struct{}),
83 targetHashes: make(map[*TargetNode]map[hash]struct{}),
84 projectName: make(map[string]string),
Bob Badoure6fdd142021-12-09 22:10:43 -080085 }
86
87 // index adds all license texts for `tn` to the index.
88 index := func(tn *TargetNode) (map[hash]struct{}, error) {
89 if hashes, ok := ni.targetHashes[tn]; ok {
90 return hashes, nil
91 }
92 hashes := make(map[hash]struct{})
93 for _, text := range tn.LicenseTexts() {
Bob Badour5028abc2022-02-09 11:56:58 -080094 fname := strings.SplitN(text, ":", 2)[0]
95 if _, ok := ni.hash[fname]; !ok {
96 err := ni.addText(fname)
Bob Badoure6fdd142021-12-09 22:10:43 -080097 if err != nil {
98 return nil, err
99 }
100 }
Bob Badour5028abc2022-02-09 11:56:58 -0800101 hash := ni.hash[fname]
Bob Badoure6fdd142021-12-09 22:10:43 -0800102 if _, ok := hashes[hash]; !ok {
103 hashes[hash] = struct{}{}
104 }
105 }
106 ni.targetHashes[tn] = hashes
107 return hashes, nil
108 }
109
Bob Badourab5cfbd2022-10-17 17:40:04 -0700110 link := func(tn *TargetNode, hashes map[hash]struct{}, installPaths []string) error {
Bob Badoure6fdd142021-12-09 22:10:43 -0800111 for h := range hashes {
Bob Badourab5cfbd2022-10-17 17:40:04 -0700112 libName, err := ni.getLibName(tn, h)
113 if err != nil {
114 return err
115 }
Bob Badour5028abc2022-02-09 11:56:58 -0800116 if _, ok := ni.libHash[libName]; !ok {
117 ni.libHash[libName] = make(map[hash]struct{})
118 }
Bob Badoure6fdd142021-12-09 22:10:43 -0800119 if _, ok := ni.hashLibInstall[h]; !ok {
120 ni.hashLibInstall[h] = make(map[string]map[string]struct{})
121 }
122 if _, ok := ni.libHash[libName][h]; !ok {
123 ni.libHash[libName][h] = struct{}{}
124 }
125 for _, installPath := range installPaths {
Bob Badour6ea14572022-01-23 17:15:46 -0800126 if _, ok := ni.installHashLib[installPath]; !ok {
127 ni.installHashLib[installPath] = make(map[hash]map[string]struct{})
128 ni.installHashLib[installPath][h] = make(map[string]struct{})
129 ni.installHashLib[installPath][h][libName] = struct{}{}
130 } else if _, ok = ni.installHashLib[installPath][h]; !ok {
131 ni.installHashLib[installPath][h] = make(map[string]struct{})
132 ni.installHashLib[installPath][h][libName] = struct{}{}
133 } else if _, ok = ni.installHashLib[installPath][h][libName]; !ok {
134 ni.installHashLib[installPath][h][libName] = struct{}{}
Bob Badoure6fdd142021-12-09 22:10:43 -0800135 }
136 if _, ok := ni.hashLibInstall[h]; !ok {
137 ni.hashLibInstall[h] = make(map[string]map[string]struct{})
138 ni.hashLibInstall[h][libName] = make(map[string]struct{})
139 ni.hashLibInstall[h][libName][installPath] = struct{}{}
140 } else if _, ok = ni.hashLibInstall[h][libName]; !ok {
141 ni.hashLibInstall[h][libName] = make(map[string]struct{})
142 ni.hashLibInstall[h][libName][installPath] = struct{}{}
143 } else if _, ok = ni.hashLibInstall[h][libName][installPath]; !ok {
144 ni.hashLibInstall[h][libName][installPath] = struct{}{}
145 }
146 }
147 }
Bob Badourab5cfbd2022-10-17 17:40:04 -0700148 return nil
149 }
150
151 cacheMetadata := func(tn *TargetNode) {
152 ni.pmix.MetadataForProjects(tn.Projects()...)
Bob Badoure6fdd142021-12-09 22:10:43 -0800153 }
154
155 // returns error from walk below.
156 var err error
157
158 WalkTopDown(NoEdgeContext{}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
159 if err != nil {
160 return false
161 }
162 if !ni.shipped.Contains(tn) {
163 return false
164 }
Bob Badourab5cfbd2022-10-17 17:40:04 -0700165 go cacheMetadata(tn)
Bob Badoure6fdd142021-12-09 22:10:43 -0800166 installPaths := getInstallPaths(tn, path)
167 var hashes map[hash]struct{}
168 hashes, err = index(tn)
169 if err != nil {
170 return false
171 }
Bob Badourab5cfbd2022-10-17 17:40:04 -0700172 err = link(tn, hashes, installPaths)
173 if err != nil {
174 return false
175 }
Bob Badoure6fdd142021-12-09 22:10:43 -0800176 if tn.IsContainer() {
177 return true
178 }
179
180 for _, r := range rs.Resolutions(tn) {
181 hashes, err = index(r.actsOn)
182 if err != nil {
183 return false
184 }
Bob Badourab5cfbd2022-10-17 17:40:04 -0700185 err = link(r.actsOn, hashes, installPaths)
186 if err != nil {
187 return false
188 }
Bob Badoure6fdd142021-12-09 22:10:43 -0800189 }
190 return false
191 })
192
193 if err != nil {
194 return nil, err
195 }
196
197 return ni, nil
198}
199
200// Hashes returns an ordered channel of the hashed license texts.
201func (ni *NoticeIndex) Hashes() chan hash {
202 c := make(chan hash)
203 go func() {
204 libs := make([]string, 0, len(ni.libHash))
205 for libName := range ni.libHash {
206 libs = append(libs, libName)
207 }
208 sort.Strings(libs)
209 hashes := make(map[hash]struct{})
210 for _, libName := range libs {
211 hl := make([]hash, 0, len(ni.libHash[libName]))
212 for h := range ni.libHash[libName] {
213 if _, ok := hashes[h]; ok {
214 continue
215 }
216 hashes[h] = struct{}{}
217 hl = append(hl, h)
218 }
219 if len(hl) > 0 {
Bob Badour6ea14572022-01-23 17:15:46 -0800220 sort.Sort(hashList{ni, libName, "", &hl})
Bob Badoure6fdd142021-12-09 22:10:43 -0800221 for _, h := range hl {
222 c <- h
223 }
224 }
225 }
226 close(c)
227 }()
228 return c
Bob Badourab5cfbd2022-10-17 17:40:04 -0700229
Bob Badoure6fdd142021-12-09 22:10:43 -0800230}
231
Bob Badourab5cfbd2022-10-17 17:40:04 -0700232// InputFiles returns the complete list of files read during indexing.
233func (ni *NoticeIndex) InputFiles() []string {
234 licenseMeta := []string(nil)
235 for f := range ni.lg.targets {
236 licenseMeta = append(licenseMeta, f)
237 }
238 projectMeta := ni.pmix.AllMetadataFiles()
239 files := make([]string, 0, len(ni.files) + len(licenseMeta) + len(projectMeta))
240 files = append(files, ni.files...)
241 files = append(files, licenseMeta...)
242 files = append(files, projectMeta...)
Colin Crossbb45f8c2022-01-28 15:18:19 -0800243 return files
244}
245
Bob Badoure6fdd142021-12-09 22:10:43 -0800246// HashLibs returns the ordered array of library names using the license text
247// hashed as `h`.
248func (ni *NoticeIndex) HashLibs(h hash) []string {
249 libs := make([]string, 0, len(ni.hashLibInstall[h]))
250 for libName := range ni.hashLibInstall[h] {
251 libs = append(libs, libName)
252 }
253 sort.Strings(libs)
254 return libs
255}
256
257// HashLibInstalls returns the ordered array of install paths referencing
258// library `libName` using the license text hashed as `h`.
259func (ni *NoticeIndex) HashLibInstalls(h hash, libName string) []string {
260 installs := make([]string, 0, len(ni.hashLibInstall[h][libName]))
261 for installPath := range ni.hashLibInstall[h][libName] {
262 installs = append(installs, installPath)
263 }
264 sort.Strings(installs)
265 return installs
266}
267
Bob Badour6ea14572022-01-23 17:15:46 -0800268// InstallPaths returns the ordered channel of indexed install paths.
269func (ni *NoticeIndex) InstallPaths() chan string {
270 c := make(chan string)
271 go func() {
272 paths := make([]string, 0, len(ni.installHashLib))
273 for path := range ni.installHashLib {
274 paths = append(paths, path)
275 }
276 sort.Strings(paths)
277 for _, installPath := range paths {
278 c <- installPath
279 }
280 close(c)
281 }()
282 return c
283}
284
285// InstallHashes returns the ordered array of hashes attached to `installPath`.
286func (ni *NoticeIndex) InstallHashes(installPath string) []hash {
287 result := make([]hash, 0, len(ni.installHashLib[installPath]))
288 for h := range ni.installHashLib[installPath] {
289 result = append(result, h)
290 }
291 if len(result) > 0 {
292 sort.Sort(hashList{ni, "", installPath, &result})
293 }
294 return result
295}
296
297// InstallHashLibs returns the ordered array of library names attached to
298// `installPath` as hash `h`.
299func (ni *NoticeIndex) InstallHashLibs(installPath string, h hash) []string {
300 result := make([]string, 0, len(ni.installHashLib[installPath][h]))
301 for libName := range ni.installHashLib[installPath][h] {
302 result = append(result, libName)
303 }
304 sort.Strings(result)
305 return result
306}
307
Bob Badour00c8a382022-01-26 17:21:39 -0800308// Libraries returns the ordered channel of indexed library names.
309func (ni *NoticeIndex) Libraries() chan string {
310 c := make(chan string)
311 go func() {
312 libs := make([]string, 0, len(ni.libHash))
313 for lib := range ni.libHash {
314 libs = append(libs, lib)
315 }
316 sort.Strings(libs)
317 for _, lib := range libs {
318 c <- lib
319 }
320 close(c)
321 }()
322 return c
323}
324
Bob Badoure6fdd142021-12-09 22:10:43 -0800325// HashText returns the file content of the license text hashed as `h`.
326func (ni *NoticeIndex) HashText(h hash) []byte {
327 return ni.text[h]
328}
329
330// getLibName returns the name of the library associated with `noticeFor`.
Bob Badourab5cfbd2022-10-17 17:40:04 -0700331func (ni *NoticeIndex) getLibName(noticeFor *TargetNode, h hash) (string, error) {
Bob Badour5028abc2022-02-09 11:56:58 -0800332 for _, text := range noticeFor.LicenseTexts() {
333 if !strings.Contains(text, ":") {
Bob Badour77570052022-02-28 20:05:44 -0800334 if ni.hash[text].key != h.key {
335 continue
336 }
Bob Badourab5cfbd2022-10-17 17:40:04 -0700337 ln, err := ni.checkMetadataForLicenseText(noticeFor, text)
338 if err != nil {
339 return "", err
340 }
Bob Badour77570052022-02-28 20:05:44 -0800341 if len(ln) > 0 {
Bob Badourab5cfbd2022-10-17 17:40:04 -0700342 return ln, nil
Bob Badour77570052022-02-28 20:05:44 -0800343 }
Bob Badour5028abc2022-02-09 11:56:58 -0800344 continue
345 }
346
347 fields := strings.SplitN(text, ":", 2)
348 fname, pname := fields[0], fields[1]
349 if ni.hash[fname].key != h.key {
350 continue
351 }
352
353 ln, err := url.QueryUnescape(pname)
354 if err != nil {
355 continue
356 }
Bob Badourab5cfbd2022-10-17 17:40:04 -0700357 return ln, nil
Bob Badour5028abc2022-02-09 11:56:58 -0800358 }
Bob Badoure6fdd142021-12-09 22:10:43 -0800359 // use name from METADATA if available
Bob Badourab5cfbd2022-10-17 17:40:04 -0700360 ln, err := ni.checkMetadata(noticeFor)
361 if err != nil {
362 return "", err
363 }
Bob Badoure6fdd142021-12-09 22:10:43 -0800364 if len(ln) > 0 {
Bob Badourab5cfbd2022-10-17 17:40:04 -0700365 return ln, nil
Bob Badoure6fdd142021-12-09 22:10:43 -0800366 }
367 // use package_name: from license{} module if available
368 pn := noticeFor.PackageName()
369 if len(pn) > 0 {
Bob Badourab5cfbd2022-10-17 17:40:04 -0700370 return pn, nil
Bob Badoure6fdd142021-12-09 22:10:43 -0800371 }
372 for _, p := range noticeFor.Projects() {
373 if strings.HasPrefix(p, "prebuilts/") {
374 for _, licenseText := range noticeFor.LicenseTexts() {
375 if !strings.HasPrefix(licenseText, "prebuilts/") {
376 continue
377 }
Bob Badour77570052022-02-28 20:05:44 -0800378 if !strings.Contains(licenseText, ":") {
379 if ni.hash[licenseText].key != h.key {
380 continue
381 }
382 } else {
383 fields := strings.SplitN(licenseText, ":", 2)
384 fname := fields[0]
385 if ni.hash[fname].key != h.key {
386 continue
387 }
388 }
Colin Cross4b545252022-09-28 15:36:53 -0700389 for _, safePrebuiltPrefix := range safePrebuiltPrefixes {
390 match := safePrebuiltPrefix.re.FindString(licenseText)
Bob Badoure6fdd142021-12-09 22:10:43 -0800391 if len(match) == 0 {
392 continue
393 }
Colin Cross4b545252022-09-28 15:36:53 -0700394 if safePrebuiltPrefix.strip {
Bob Badoure6fdd142021-12-09 22:10:43 -0800395 // strip entire prefix
396 match = licenseText[len(match):]
397 } else {
398 // strip from prebuilts/ until safe prefix
Colin Cross4b545252022-09-28 15:36:53 -0700399 match = licenseText[len(match)-len(safePrebuiltPrefix.prefix):]
Bob Badoure6fdd142021-12-09 22:10:43 -0800400 }
401 // remove LICENSE or NOTICE or other filename
402 li := strings.LastIndex(match, "/")
Bob Badoure9b38c12022-02-09 15:56:59 -0800403 if li > 0 {
Bob Badoure6fdd142021-12-09 22:10:43 -0800404 match = match[:li]
405 }
406 // remove *licenses/ path segment and subdirectory if in path
Bob Badoure9b38c12022-02-09 15:56:59 -0800407 if offsets := licensesPathRegexp.FindAllStringIndex(match, -1); offsets != nil && offsets[len(offsets)-1][0] > 0 {
Bob Badoure6fdd142021-12-09 22:10:43 -0800408 match = match[:offsets[len(offsets)-1][0]]
409 li = strings.LastIndex(match, "/")
Bob Badoure9b38c12022-02-09 15:56:59 -0800410 if li > 0 {
Bob Badoure6fdd142021-12-09 22:10:43 -0800411 match = match[:li]
412 }
413 }
Bob Badourab5cfbd2022-10-17 17:40:04 -0700414 return match, nil
Bob Badoure6fdd142021-12-09 22:10:43 -0800415 }
416 break
417 }
418 }
Colin Cross4b545252022-09-28 15:36:53 -0700419 for _, safePathPrefix := range safePathPrefixes {
420 if strings.HasPrefix(p, safePathPrefix.prefix) {
421 if safePathPrefix.strip {
Bob Badourab5cfbd2022-10-17 17:40:04 -0700422 return p[len(safePathPrefix.prefix):], nil
Bob Badoure6fdd142021-12-09 22:10:43 -0800423 } else {
Bob Badourab5cfbd2022-10-17 17:40:04 -0700424 return p, nil
Bob Badoure6fdd142021-12-09 22:10:43 -0800425 }
426 }
427 }
428 }
429 // strip off [./]meta_lic from license metadata path and extract base name
430 n := noticeFor.name[:len(noticeFor.name)-9]
431 li := strings.LastIndex(n, "/")
Bob Badoure9b38c12022-02-09 15:56:59 -0800432 if li > 0 {
Bob Badoure6fdd142021-12-09 22:10:43 -0800433 n = n[li+1:]
434 }
Bob Badour77570052022-02-28 20:05:44 -0800435 fi := strings.Index(n, "@")
436 if fi > 0 {
437 n = n[:fi]
438 }
Bob Badourab5cfbd2022-10-17 17:40:04 -0700439 return n, nil
Bob Badoure6fdd142021-12-09 22:10:43 -0800440}
441
442// checkMetadata tries to look up a library name from a METADATA file associated with `noticeFor`.
Bob Badourab5cfbd2022-10-17 17:40:04 -0700443func (ni *NoticeIndex) checkMetadata(noticeFor *TargetNode) (string, error) {
444 pms, err := ni.pmix.MetadataForProjects(noticeFor.Projects()...)
445 if err != nil {
446 return "", err
Bob Badoure6fdd142021-12-09 22:10:43 -0800447 }
Bob Badourab5cfbd2022-10-17 17:40:04 -0700448 for _, pm := range pms {
449 name := pm.VersionedName()
450 if name != "" {
451 return name, nil
452 }
453 }
454 return "", nil
Bob Badoure6fdd142021-12-09 22:10:43 -0800455}
456
Bob Badour77570052022-02-28 20:05:44 -0800457// checkMetadataForLicenseText
Bob Badourab5cfbd2022-10-17 17:40:04 -0700458func (ni *NoticeIndex) checkMetadataForLicenseText(noticeFor *TargetNode, licenseText string) (string, error) {
Bob Badour77570052022-02-28 20:05:44 -0800459 p := ""
460 for _, proj := range noticeFor.Projects() {
461 if strings.HasPrefix(licenseText, proj) {
462 p = proj
463 }
464 }
465 if len(p) == 0 {
466 p = filepath.Dir(licenseText)
467 for {
468 fi, err := fs.Stat(ni.rootFS, filepath.Join(p, ".git"))
469 if err == nil && fi.IsDir() {
470 break
471 }
472 if strings.Contains(p, "/") && p != "/" {
473 p = filepath.Dir(p)
474 continue
475 }
Bob Badourab5cfbd2022-10-17 17:40:04 -0700476 return "", nil
Bob Badour77570052022-02-28 20:05:44 -0800477 }
478 }
Bob Badourab5cfbd2022-10-17 17:40:04 -0700479 pms, err := ni.pmix.MetadataForProjects(p)
Bob Badour77570052022-02-28 20:05:44 -0800480 if err != nil {
481 return "", err
482 }
Bob Badourab5cfbd2022-10-17 17:40:04 -0700483 if pms == nil {
484 return "", nil
Bob Badour77570052022-02-28 20:05:44 -0800485 }
Bob Badourab5cfbd2022-10-17 17:40:04 -0700486 return pms[0].VersionedName(), nil
Bob Badour77570052022-02-28 20:05:44 -0800487}
488
Bob Badoure6fdd142021-12-09 22:10:43 -0800489// addText reads and indexes the content of a license text file.
490func (ni *NoticeIndex) addText(file string) error {
491 f, err := ni.rootFS.Open(filepath.Clean(file))
492 if err != nil {
493 return fmt.Errorf("error opening license text file %q: %w", file, err)
494 }
495
496 // read the file
497 text, err := io.ReadAll(f)
498 if err != nil {
499 return fmt.Errorf("error reading license text file %q: %w", file, err)
500 }
501
502 hash := hash{fmt.Sprintf("%x", md5.Sum(text))}
503 ni.hash[file] = hash
504 if _, alreadyPresent := ni.text[hash]; !alreadyPresent {
505 ni.text[hash] = text
506 }
507
Colin Crossbb45f8c2022-01-28 15:18:19 -0800508 ni.files = append(ni.files, file)
509
Bob Badoure6fdd142021-12-09 22:10:43 -0800510 return nil
511}
512
513// getInstallPaths returns the names of the used dependencies mapped to their
514// installed locations.
515func getInstallPaths(attachesTo *TargetNode, path TargetEdgePath) []string {
516 if len(path) == 0 {
517 installs := attachesTo.Installed()
518 if 0 == len(installs) {
519 installs = attachesTo.Built()
520 }
521 return installs
522 }
523
524 var getInstalls func(path TargetEdgePath) []string
525
526 getInstalls = func(path TargetEdgePath) []string {
527 // deps contains the output targets from the dependencies in the path
528 var deps []string
529 if len(path) > 1 {
530 // recursively get the targets from the sub-path skipping 1 path segment
531 deps = getInstalls(path[1:])
532 } else {
533 // stop recursion at 1 path segment
534 deps = path[0].Dependency().TargetFiles()
535 }
536 size := 0
537 prefixes := path[0].Target().TargetFiles()
538 installMap := path[0].Target().InstallMap()
539 sources := path[0].Target().Sources()
540 for _, dep := range deps {
541 found := false
542 for _, source := range sources {
543 if strings.HasPrefix(dep, source) {
544 found = true
545 break
546 }
547 }
548 if !found {
549 continue
550 }
551 for _, im := range installMap {
552 if strings.HasPrefix(dep, im.FromPath) {
553 size += len(prefixes)
554 break
555 }
556 }
557 }
558
559 installs := make([]string, 0, size)
560 for _, dep := range deps {
561 found := false
562 for _, source := range sources {
563 if strings.HasPrefix(dep, source) {
564 found = true
565 break
566 }
567 }
568 if !found {
569 continue
570 }
571 for _, im := range installMap {
572 if strings.HasPrefix(dep, im.FromPath) {
573 for _, prefix := range prefixes {
574 installs = append(installs, prefix+im.ContainerPath+dep[len(im.FromPath):])
575 }
576 break
577 }
578 }
579 }
580 return installs
581 }
582 allInstalls := getInstalls(path)
583 installs := path[0].Target().Installed()
584 if len(installs) == 0 {
585 return allInstalls
586 }
587 result := make([]string, 0, len(allInstalls))
588 for _, install := range allInstalls {
589 for _, prefix := range installs {
590 if strings.HasPrefix(install, prefix) {
591 result = append(result, install)
592 }
593 }
594 }
595 return result
596}
597
598// hash is an opaque string derived from md5sum.
599type hash struct {
600 key string
601}
602
603// String returns the hexadecimal representation of the hash.
604func (h hash) String() string {
605 return h.key
606}
607
608// hashList orders an array of hashes
609type hashList struct {
Bob Badour6ea14572022-01-23 17:15:46 -0800610 ni *NoticeIndex
611 libName string
612 installPath string
613 hashes *[]hash
Bob Badoure6fdd142021-12-09 22:10:43 -0800614}
615
616// Len returns the count of elements in the slice.
617func (l hashList) Len() int { return len(*l.hashes) }
618
619// Swap rearranges 2 elements of the slice so that each occupies the other's
620// former position.
621func (l hashList) Swap(i, j int) { (*l.hashes)[i], (*l.hashes)[j] = (*l.hashes)[j], (*l.hashes)[i] }
622
623// Less returns true when the `i`th element is lexicographically less than
624// the `j`th element.
625func (l hashList) Less(i, j int) bool {
626 var insti, instj int
Bob Badoure9b38c12022-02-09 15:56:59 -0800627 if len(l.libName) > 0 {
Bob Badoure6fdd142021-12-09 22:10:43 -0800628 insti = len(l.ni.hashLibInstall[(*l.hashes)[i]][l.libName])
629 instj = len(l.ni.hashLibInstall[(*l.hashes)[j]][l.libName])
Bob Badour6ea14572022-01-23 17:15:46 -0800630 } else {
631 libsi := l.ni.InstallHashLibs(l.installPath, (*l.hashes)[i])
632 libsj := l.ni.InstallHashLibs(l.installPath, (*l.hashes)[j])
633 libsis := strings.Join(libsi, " ")
634 libsjs := strings.Join(libsj, " ")
635 if libsis != libsjs {
636 return libsis < libsjs
637 }
Bob Badoure6fdd142021-12-09 22:10:43 -0800638 }
639 if insti == instj {
640 leni := len(l.ni.text[(*l.hashes)[i]])
641 lenj := len(l.ni.text[(*l.hashes)[j]])
642 if leni == lenj {
643 // all else equal, just order by hash value
644 return (*l.hashes)[i].key < (*l.hashes)[j].key
645 }
646 // put shortest texts first within same # of installs
647 return leni < lenj
648 }
649 // reverse order of # installs so that most popular appears first
650 return instj < insti
651}