blob: 86d42ac560fe1e5eb2e54a94a4bd5904a9e0b38a [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 (
18 "bufio"
19 "crypto/md5"
20 "fmt"
21 "io"
22 "io/fs"
Bob Badour5028abc2022-02-09 11:56:58 -080023 "net/url"
Bob Badoure6fdd142021-12-09 22:10:43 -080024 "path/filepath"
25 "regexp"
26 "sort"
27 "strings"
28)
29
30const (
31 noProjectName = "\u2205"
32)
33
34var (
35 nameRegexp = regexp.MustCompile(`^\s*name\s*:\s*"(.*)"\s*$`)
36 descRegexp = regexp.MustCompile(`^\s*description\s*:\s*"(.*)"\s*$`)
37 versionRegexp = regexp.MustCompile(`^\s*version\s*:\s*"(.*)"\s*$`)
38 licensesPathRegexp = regexp.MustCompile(`licen[cs]es?/`)
39)
40
41// NoticeIndex transforms license metadata into license text hashes, library
42// names, and install paths indexing them for fast lookup/iteration.
43type NoticeIndex struct {
44 // lg identifies the license graph to which the index applies.
45 lg *LicenseGraph
46 // rs identifies the set of resolutions upon which the index is based.
47 rs ResolutionSet
48 // shipped identifies the set of target nodes shipped directly or as derivative works.
49 shipped *TargetNodeSet
50 // rootFS locates the root of the file system from which to read the files.
51 rootFS fs.FS
52 // hash maps license text filenames to content hashes
53 hash map[string]hash
54 // text maps content hashes to content
55 text map[hash][]byte
56 // hashLibInstall maps hashes to libraries to install paths.
57 hashLibInstall map[hash]map[string]map[string]struct{}
Bob Badour6ea14572022-01-23 17:15:46 -080058 // installHashLib maps install paths to libraries to hashes.
59 installHashLib map[string]map[hash]map[string]struct{}
Bob Badoure6fdd142021-12-09 22:10:43 -080060 // libHash maps libraries to hashes.
61 libHash map[string]map[hash]struct{}
62 // targetHash maps target nodes to hashes.
63 targetHashes map[*TargetNode]map[hash]struct{}
64 // projectName maps project directory names to project name text.
65 projectName map[string]string
Colin Crossbb45f8c2022-01-28 15:18:19 -080066 // files lists all the files accessed during indexing
67 files []string
Bob Badoure6fdd142021-12-09 22:10:43 -080068}
69
70// IndexLicenseTexts creates a hashed index of license texts for `lg` and `rs`
71// using the files rooted at `rootFS`.
72func IndexLicenseTexts(rootFS fs.FS, lg *LicenseGraph, rs ResolutionSet) (*NoticeIndex, error) {
73 if rs == nil {
74 rs = ResolveNotices(lg)
75 }
76 ni := &NoticeIndex{
Colin Crossbb45f8c2022-01-28 15:18:19 -080077 lg: lg,
78 rs: rs,
79 shipped: ShippedNodes(lg),
80 rootFS: rootFS,
81 hash: make(map[string]hash),
82 text: make(map[hash][]byte),
83 hashLibInstall: make(map[hash]map[string]map[string]struct{}),
84 installHashLib: make(map[string]map[hash]map[string]struct{}),
85 libHash: make(map[string]map[hash]struct{}),
86 targetHashes: make(map[*TargetNode]map[hash]struct{}),
87 projectName: make(map[string]string),
Bob Badoure6fdd142021-12-09 22:10:43 -080088 }
89
90 // index adds all license texts for `tn` to the index.
91 index := func(tn *TargetNode) (map[hash]struct{}, error) {
92 if hashes, ok := ni.targetHashes[tn]; ok {
93 return hashes, nil
94 }
95 hashes := make(map[hash]struct{})
96 for _, text := range tn.LicenseTexts() {
Bob Badour5028abc2022-02-09 11:56:58 -080097 fname := strings.SplitN(text, ":", 2)[0]
98 if _, ok := ni.hash[fname]; !ok {
99 err := ni.addText(fname)
Bob Badoure6fdd142021-12-09 22:10:43 -0800100 if err != nil {
101 return nil, err
102 }
103 }
Bob Badour5028abc2022-02-09 11:56:58 -0800104 hash := ni.hash[fname]
Bob Badoure6fdd142021-12-09 22:10:43 -0800105 if _, ok := hashes[hash]; !ok {
106 hashes[hash] = struct{}{}
107 }
108 }
109 ni.targetHashes[tn] = hashes
110 return hashes, nil
111 }
112
Bob Badour5028abc2022-02-09 11:56:58 -0800113 link := func(tn *TargetNode, hashes map[hash]struct{}, installPaths []string) {
Bob Badoure6fdd142021-12-09 22:10:43 -0800114 for h := range hashes {
Bob Badour5028abc2022-02-09 11:56:58 -0800115 libName := ni.getLibName(tn, h)
116 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 }
148 }
149
150 // returns error from walk below.
151 var err error
152
153 WalkTopDown(NoEdgeContext{}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
154 if err != nil {
155 return false
156 }
157 if !ni.shipped.Contains(tn) {
158 return false
159 }
160 installPaths := getInstallPaths(tn, path)
161 var hashes map[hash]struct{}
162 hashes, err = index(tn)
163 if err != nil {
164 return false
165 }
Bob Badour5028abc2022-02-09 11:56:58 -0800166 link(tn, hashes, installPaths)
Bob Badoure6fdd142021-12-09 22:10:43 -0800167 if tn.IsContainer() {
168 return true
169 }
170
171 for _, r := range rs.Resolutions(tn) {
172 hashes, err = index(r.actsOn)
173 if err != nil {
174 return false
175 }
Bob Badour5028abc2022-02-09 11:56:58 -0800176 link(r.actsOn, hashes, installPaths)
Bob Badoure6fdd142021-12-09 22:10:43 -0800177 }
178 return false
179 })
180
181 if err != nil {
182 return nil, err
183 }
184
185 return ni, nil
186}
187
188// Hashes returns an ordered channel of the hashed license texts.
189func (ni *NoticeIndex) Hashes() chan hash {
190 c := make(chan hash)
191 go func() {
192 libs := make([]string, 0, len(ni.libHash))
193 for libName := range ni.libHash {
194 libs = append(libs, libName)
195 }
196 sort.Strings(libs)
197 hashes := make(map[hash]struct{})
198 for _, libName := range libs {
199 hl := make([]hash, 0, len(ni.libHash[libName]))
200 for h := range ni.libHash[libName] {
201 if _, ok := hashes[h]; ok {
202 continue
203 }
204 hashes[h] = struct{}{}
205 hl = append(hl, h)
206 }
207 if len(hl) > 0 {
Bob Badour6ea14572022-01-23 17:15:46 -0800208 sort.Sort(hashList{ni, libName, "", &hl})
Bob Badoure6fdd142021-12-09 22:10:43 -0800209 for _, h := range hl {
210 c <- h
211 }
212 }
213 }
214 close(c)
215 }()
216 return c
217}
218
Colin Crossbb45f8c2022-01-28 15:18:19 -0800219// InputNoticeFiles returns the list of files that were hashed during IndexLicenseTexts.
220func (ni *NoticeIndex) InputNoticeFiles() []string {
221 files := append([]string(nil), ni.files...)
222 sort.Strings(files)
223 return files
224}
225
Bob Badoure6fdd142021-12-09 22:10:43 -0800226// HashLibs returns the ordered array of library names using the license text
227// hashed as `h`.
228func (ni *NoticeIndex) HashLibs(h hash) []string {
229 libs := make([]string, 0, len(ni.hashLibInstall[h]))
230 for libName := range ni.hashLibInstall[h] {
231 libs = append(libs, libName)
232 }
233 sort.Strings(libs)
234 return libs
235}
236
237// HashLibInstalls returns the ordered array of install paths referencing
238// library `libName` using the license text hashed as `h`.
239func (ni *NoticeIndex) HashLibInstalls(h hash, libName string) []string {
240 installs := make([]string, 0, len(ni.hashLibInstall[h][libName]))
241 for installPath := range ni.hashLibInstall[h][libName] {
242 installs = append(installs, installPath)
243 }
244 sort.Strings(installs)
245 return installs
246}
247
Bob Badour6ea14572022-01-23 17:15:46 -0800248// InstallPaths returns the ordered channel of indexed install paths.
249func (ni *NoticeIndex) InstallPaths() chan string {
250 c := make(chan string)
251 go func() {
252 paths := make([]string, 0, len(ni.installHashLib))
253 for path := range ni.installHashLib {
254 paths = append(paths, path)
255 }
256 sort.Strings(paths)
257 for _, installPath := range paths {
258 c <- installPath
259 }
260 close(c)
261 }()
262 return c
263}
264
265// InstallHashes returns the ordered array of hashes attached to `installPath`.
266func (ni *NoticeIndex) InstallHashes(installPath string) []hash {
267 result := make([]hash, 0, len(ni.installHashLib[installPath]))
268 for h := range ni.installHashLib[installPath] {
269 result = append(result, h)
270 }
271 if len(result) > 0 {
272 sort.Sort(hashList{ni, "", installPath, &result})
273 }
274 return result
275}
276
277// InstallHashLibs returns the ordered array of library names attached to
278// `installPath` as hash `h`.
279func (ni *NoticeIndex) InstallHashLibs(installPath string, h hash) []string {
280 result := make([]string, 0, len(ni.installHashLib[installPath][h]))
281 for libName := range ni.installHashLib[installPath][h] {
282 result = append(result, libName)
283 }
284 sort.Strings(result)
285 return result
286}
287
Bob Badour00c8a382022-01-26 17:21:39 -0800288// Libraries returns the ordered channel of indexed library names.
289func (ni *NoticeIndex) Libraries() chan string {
290 c := make(chan string)
291 go func() {
292 libs := make([]string, 0, len(ni.libHash))
293 for lib := range ni.libHash {
294 libs = append(libs, lib)
295 }
296 sort.Strings(libs)
297 for _, lib := range libs {
298 c <- lib
299 }
300 close(c)
301 }()
302 return c
303}
304
Bob Badoure6fdd142021-12-09 22:10:43 -0800305// HashText returns the file content of the license text hashed as `h`.
306func (ni *NoticeIndex) HashText(h hash) []byte {
307 return ni.text[h]
308}
309
310// getLibName returns the name of the library associated with `noticeFor`.
Bob Badour5028abc2022-02-09 11:56:58 -0800311func (ni *NoticeIndex) getLibName(noticeFor *TargetNode, h hash) string {
312 for _, text := range noticeFor.LicenseTexts() {
313 if !strings.Contains(text, ":") {
Bob Badour77570052022-02-28 20:05:44 -0800314 if ni.hash[text].key != h.key {
315 continue
316 }
317 ln := ni.checkMetadataForLicenseText(noticeFor, text)
318 if len(ln) > 0 {
319 return ln
320 }
Bob Badour5028abc2022-02-09 11:56:58 -0800321 continue
322 }
323
324 fields := strings.SplitN(text, ":", 2)
325 fname, pname := fields[0], fields[1]
326 if ni.hash[fname].key != h.key {
327 continue
328 }
329
330 ln, err := url.QueryUnescape(pname)
331 if err != nil {
332 continue
333 }
334 return ln
335 }
Bob Badoure6fdd142021-12-09 22:10:43 -0800336 // use name from METADATA if available
337 ln := ni.checkMetadata(noticeFor)
338 if len(ln) > 0 {
339 return ln
340 }
341 // use package_name: from license{} module if available
342 pn := noticeFor.PackageName()
343 if len(pn) > 0 {
344 return pn
345 }
346 for _, p := range noticeFor.Projects() {
347 if strings.HasPrefix(p, "prebuilts/") {
348 for _, licenseText := range noticeFor.LicenseTexts() {
349 if !strings.HasPrefix(licenseText, "prebuilts/") {
350 continue
351 }
Bob Badour77570052022-02-28 20:05:44 -0800352 if !strings.Contains(licenseText, ":") {
353 if ni.hash[licenseText].key != h.key {
354 continue
355 }
356 } else {
357 fields := strings.SplitN(licenseText, ":", 2)
358 fname := fields[0]
359 if ni.hash[fname].key != h.key {
360 continue
361 }
362 }
Colin Cross4b545252022-09-28 15:36:53 -0700363 for _, safePrebuiltPrefix := range safePrebuiltPrefixes {
364 match := safePrebuiltPrefix.re.FindString(licenseText)
Bob Badoure6fdd142021-12-09 22:10:43 -0800365 if len(match) == 0 {
366 continue
367 }
Colin Cross4b545252022-09-28 15:36:53 -0700368 if safePrebuiltPrefix.strip {
Bob Badoure6fdd142021-12-09 22:10:43 -0800369 // strip entire prefix
370 match = licenseText[len(match):]
371 } else {
372 // strip from prebuilts/ until safe prefix
Colin Cross4b545252022-09-28 15:36:53 -0700373 match = licenseText[len(match)-len(safePrebuiltPrefix.prefix):]
Bob Badoure6fdd142021-12-09 22:10:43 -0800374 }
375 // remove LICENSE or NOTICE or other filename
376 li := strings.LastIndex(match, "/")
Bob Badoure9b38c12022-02-09 15:56:59 -0800377 if li > 0 {
Bob Badoure6fdd142021-12-09 22:10:43 -0800378 match = match[:li]
379 }
380 // remove *licenses/ path segment and subdirectory if in path
Bob Badoure9b38c12022-02-09 15:56:59 -0800381 if offsets := licensesPathRegexp.FindAllStringIndex(match, -1); offsets != nil && offsets[len(offsets)-1][0] > 0 {
Bob Badoure6fdd142021-12-09 22:10:43 -0800382 match = match[:offsets[len(offsets)-1][0]]
383 li = strings.LastIndex(match, "/")
Bob Badoure9b38c12022-02-09 15:56:59 -0800384 if li > 0 {
Bob Badoure6fdd142021-12-09 22:10:43 -0800385 match = match[:li]
386 }
387 }
388 return match
389 }
390 break
391 }
392 }
Colin Cross4b545252022-09-28 15:36:53 -0700393 for _, safePathPrefix := range safePathPrefixes {
394 if strings.HasPrefix(p, safePathPrefix.prefix) {
395 if safePathPrefix.strip {
396 return p[len(safePathPrefix.prefix):]
Bob Badoure6fdd142021-12-09 22:10:43 -0800397 } else {
398 return p
399 }
400 }
401 }
402 }
403 // strip off [./]meta_lic from license metadata path and extract base name
404 n := noticeFor.name[:len(noticeFor.name)-9]
405 li := strings.LastIndex(n, "/")
Bob Badoure9b38c12022-02-09 15:56:59 -0800406 if li > 0 {
Bob Badoure6fdd142021-12-09 22:10:43 -0800407 n = n[li+1:]
408 }
Bob Badour77570052022-02-28 20:05:44 -0800409 fi := strings.Index(n, "@")
410 if fi > 0 {
411 n = n[:fi]
412 }
Bob Badoure6fdd142021-12-09 22:10:43 -0800413 return n
414}
415
416// checkMetadata tries to look up a library name from a METADATA file associated with `noticeFor`.
417func (ni *NoticeIndex) checkMetadata(noticeFor *TargetNode) string {
418 for _, p := range noticeFor.Projects() {
419 if name, ok := ni.projectName[p]; ok {
420 if name == noProjectName {
421 continue
422 }
423 return name
424 }
Bob Badour77570052022-02-28 20:05:44 -0800425 name, err := ni.checkMetadataFile(filepath.Join(p, "METADATA"))
Bob Badoure6fdd142021-12-09 22:10:43 -0800426 if err != nil {
427 ni.projectName[p] = noProjectName
428 continue
429 }
Bob Badour77570052022-02-28 20:05:44 -0800430 if len(name) == 0 {
431 ni.projectName[p] = noProjectName
432 continue
Bob Badoure6fdd142021-12-09 22:10:43 -0800433 }
Bob Badour77570052022-02-28 20:05:44 -0800434 ni.projectName[p] = name
435 return name
Bob Badoure6fdd142021-12-09 22:10:43 -0800436 }
437 return ""
438}
439
Bob Badour77570052022-02-28 20:05:44 -0800440// checkMetadataForLicenseText
441func (ni *NoticeIndex) checkMetadataForLicenseText(noticeFor *TargetNode, licenseText string) string {
442 p := ""
443 for _, proj := range noticeFor.Projects() {
444 if strings.HasPrefix(licenseText, proj) {
445 p = proj
446 }
447 }
448 if len(p) == 0 {
449 p = filepath.Dir(licenseText)
450 for {
451 fi, err := fs.Stat(ni.rootFS, filepath.Join(p, ".git"))
452 if err == nil && fi.IsDir() {
453 break
454 }
455 if strings.Contains(p, "/") && p != "/" {
456 p = filepath.Dir(p)
457 continue
458 }
459 return ""
460 }
461 }
462 if name, ok := ni.projectName[p]; ok {
463 if name == noProjectName {
464 return ""
465 }
466 return name
467 }
468 name, err := ni.checkMetadataFile(filepath.Join(p, "METADATA"))
469 if err == nil && len(name) > 0 {
470 ni.projectName[p] = name
471 return name
472 }
473 ni.projectName[p] = noProjectName
474 return ""
475}
476
477// checkMetadataFile tries to look up a library name from a METADATA file at `path`.
478func (ni *NoticeIndex) checkMetadataFile(path string) (string, error) {
479 f, err := ni.rootFS.Open(path)
480 if err != nil {
481 return "", err
482 }
483 name := ""
484 description := ""
485 version := ""
486 s := bufio.NewScanner(f)
487 for s.Scan() {
488 line := s.Text()
489 m := nameRegexp.FindStringSubmatch(line)
490 if m != nil {
491 if 1 < len(m) && m[1] != "" {
492 name = m[1]
493 }
494 if version != "" {
495 break
496 }
497 continue
498 }
499 m = versionRegexp.FindStringSubmatch(line)
500 if m != nil {
501 if 1 < len(m) && m[1] != "" {
502 version = m[1]
503 }
504 if name != "" {
505 break
506 }
507 continue
508 }
509 m = descRegexp.FindStringSubmatch(line)
510 if m != nil {
511 if 1 < len(m) && m[1] != "" {
512 description = m[1]
513 }
514 }
515 }
516 _ = s.Err()
517 _ = f.Close()
518 if name != "" {
519 if version != "" {
520 if version[0] == 'v' || version[0] == 'V' {
521 return name + "_" + version, nil
522 } else {
523 return name + "_v_" + version, nil
524 }
525 }
526 return name, nil
527 }
528 if description != "" {
529 return description, nil
530 }
531 return "", nil
532}
533
Bob Badoure6fdd142021-12-09 22:10:43 -0800534// addText reads and indexes the content of a license text file.
535func (ni *NoticeIndex) addText(file string) error {
536 f, err := ni.rootFS.Open(filepath.Clean(file))
537 if err != nil {
538 return fmt.Errorf("error opening license text file %q: %w", file, err)
539 }
540
541 // read the file
542 text, err := io.ReadAll(f)
543 if err != nil {
544 return fmt.Errorf("error reading license text file %q: %w", file, err)
545 }
546
547 hash := hash{fmt.Sprintf("%x", md5.Sum(text))}
548 ni.hash[file] = hash
549 if _, alreadyPresent := ni.text[hash]; !alreadyPresent {
550 ni.text[hash] = text
551 }
552
Colin Crossbb45f8c2022-01-28 15:18:19 -0800553 ni.files = append(ni.files, file)
554
Bob Badoure6fdd142021-12-09 22:10:43 -0800555 return nil
556}
557
558// getInstallPaths returns the names of the used dependencies mapped to their
559// installed locations.
560func getInstallPaths(attachesTo *TargetNode, path TargetEdgePath) []string {
561 if len(path) == 0 {
562 installs := attachesTo.Installed()
563 if 0 == len(installs) {
564 installs = attachesTo.Built()
565 }
566 return installs
567 }
568
569 var getInstalls func(path TargetEdgePath) []string
570
571 getInstalls = func(path TargetEdgePath) []string {
572 // deps contains the output targets from the dependencies in the path
573 var deps []string
574 if len(path) > 1 {
575 // recursively get the targets from the sub-path skipping 1 path segment
576 deps = getInstalls(path[1:])
577 } else {
578 // stop recursion at 1 path segment
579 deps = path[0].Dependency().TargetFiles()
580 }
581 size := 0
582 prefixes := path[0].Target().TargetFiles()
583 installMap := path[0].Target().InstallMap()
584 sources := path[0].Target().Sources()
585 for _, dep := range deps {
586 found := false
587 for _, source := range sources {
588 if strings.HasPrefix(dep, source) {
589 found = true
590 break
591 }
592 }
593 if !found {
594 continue
595 }
596 for _, im := range installMap {
597 if strings.HasPrefix(dep, im.FromPath) {
598 size += len(prefixes)
599 break
600 }
601 }
602 }
603
604 installs := make([]string, 0, size)
605 for _, dep := range deps {
606 found := false
607 for _, source := range sources {
608 if strings.HasPrefix(dep, source) {
609 found = true
610 break
611 }
612 }
613 if !found {
614 continue
615 }
616 for _, im := range installMap {
617 if strings.HasPrefix(dep, im.FromPath) {
618 for _, prefix := range prefixes {
619 installs = append(installs, prefix+im.ContainerPath+dep[len(im.FromPath):])
620 }
621 break
622 }
623 }
624 }
625 return installs
626 }
627 allInstalls := getInstalls(path)
628 installs := path[0].Target().Installed()
629 if len(installs) == 0 {
630 return allInstalls
631 }
632 result := make([]string, 0, len(allInstalls))
633 for _, install := range allInstalls {
634 for _, prefix := range installs {
635 if strings.HasPrefix(install, prefix) {
636 result = append(result, install)
637 }
638 }
639 }
640 return result
641}
642
643// hash is an opaque string derived from md5sum.
644type hash struct {
645 key string
646}
647
648// String returns the hexadecimal representation of the hash.
649func (h hash) String() string {
650 return h.key
651}
652
653// hashList orders an array of hashes
654type hashList struct {
Bob Badour6ea14572022-01-23 17:15:46 -0800655 ni *NoticeIndex
656 libName string
657 installPath string
658 hashes *[]hash
Bob Badoure6fdd142021-12-09 22:10:43 -0800659}
660
661// Len returns the count of elements in the slice.
662func (l hashList) Len() int { return len(*l.hashes) }
663
664// Swap rearranges 2 elements of the slice so that each occupies the other's
665// former position.
666func (l hashList) Swap(i, j int) { (*l.hashes)[i], (*l.hashes)[j] = (*l.hashes)[j], (*l.hashes)[i] }
667
668// Less returns true when the `i`th element is lexicographically less than
669// the `j`th element.
670func (l hashList) Less(i, j int) bool {
671 var insti, instj int
Bob Badoure9b38c12022-02-09 15:56:59 -0800672 if len(l.libName) > 0 {
Bob Badoure6fdd142021-12-09 22:10:43 -0800673 insti = len(l.ni.hashLibInstall[(*l.hashes)[i]][l.libName])
674 instj = len(l.ni.hashLibInstall[(*l.hashes)[j]][l.libName])
Bob Badour6ea14572022-01-23 17:15:46 -0800675 } else {
676 libsi := l.ni.InstallHashLibs(l.installPath, (*l.hashes)[i])
677 libsj := l.ni.InstallHashLibs(l.installPath, (*l.hashes)[j])
678 libsis := strings.Join(libsi, " ")
679 libsjs := strings.Join(libsj, " ")
680 if libsis != libsjs {
681 return libsis < libsjs
682 }
Bob Badoure6fdd142021-12-09 22:10:43 -0800683 }
684 if insti == instj {
685 leni := len(l.ni.text[(*l.hashes)[i]])
686 lenj := len(l.ni.text[(*l.hashes)[j]])
687 if leni == lenj {
688 // all else equal, just order by hash value
689 return (*l.hashes)[i].key < (*l.hashes)[j].key
690 }
691 // put shortest texts first within same # of installs
692 return leni < lenj
693 }
694 // reverse order of # installs so that most popular appears first
695 return instj < insti
696}