license metadata html notice files

Introduce the below command-line tool:

htmlnotice outputs a NOTICE.html file constructed from the license
texts of the transitive closure of dependencies.

Bug: 68860345
Bug: 151177513
Bug: 151953481
Bug: 213388645
Bug: 210912771

Test: m all
Test: m systemlicense
Test: m htmlnotice; out/soong/host/linux-x85/htmlnotice ...

where ... is the path to the .meta_lic file for the system image. In my
case if

$ export PRODUCT=$(realpath $ANDROID_PRODUCT_OUT --relative-to=$PWD)

... can be expressed as:

${PRODUCT}/gen/META/lic_intermediates/${PRODUCT}/system.img.meta_lic

Change-Id: Idbbeb2939d8cbf497237516fe468004fcd2d72a1
diff --git a/tools/compliance/noticeindex.go b/tools/compliance/noticeindex.go
index 06c2627..5b7f376 100644
--- a/tools/compliance/noticeindex.go
+++ b/tools/compliance/noticeindex.go
@@ -54,8 +54,8 @@
 	text map[hash][]byte
 	// hashLibInstall maps hashes to libraries to install paths.
 	hashLibInstall map[hash]map[string]map[string]struct{}
-	// installLibHash maps install paths to libraries to hashes.
-	installLibHash map[string]map[string]map[hash]struct{}
+	// installHashLib maps install paths to libraries to hashes.
+	installHashLib map[string]map[hash]map[string]struct{}
 	// libHash maps libraries to hashes.
 	libHash map[string]map[hash]struct{}
 	// targetHash maps target nodes to hashes.
@@ -75,7 +75,7 @@
 		make(map[string]hash),
 		make(map[hash][]byte),
 		make(map[hash]map[string]map[string]struct{}),
-		make(map[string]map[string]map[hash]struct{}),
+		make(map[string]map[hash]map[string]struct{}),
 		make(map[string]map[hash]struct{}),
 		make(map[*TargetNode]map[hash]struct{}),
 		make(map[string]string),
@@ -115,15 +115,15 @@
 				ni.libHash[libName][h] = struct{}{}
 			}
 			for _, installPath := range installPaths {
-				if _, ok := ni.installLibHash[installPath]; !ok {
-					ni.installLibHash[installPath] = make(map[string]map[hash]struct{})
-					ni.installLibHash[installPath][libName] = make(map[hash]struct{})
-					ni.installLibHash[installPath][libName][h] = struct{}{}
-				} else if _, ok = ni.installLibHash[installPath][libName]; !ok {
-					ni.installLibHash[installPath][libName] = make(map[hash]struct{})
-					ni.installLibHash[installPath][libName][h] = struct{}{}
-				} else if _, ok = ni.installLibHash[installPath][libName][h]; !ok {
-					ni.installLibHash[installPath][libName][h] = struct{}{}
+				if _, ok := ni.installHashLib[installPath]; !ok {
+					ni.installHashLib[installPath] = make(map[hash]map[string]struct{})
+					ni.installHashLib[installPath][h] = make(map[string]struct{})
+					ni.installHashLib[installPath][h][libName] = struct{}{}
+				} else if _, ok = ni.installHashLib[installPath][h]; !ok {
+					ni.installHashLib[installPath][h] = make(map[string]struct{})
+					ni.installHashLib[installPath][h][libName] = struct{}{}
+				} else if _, ok = ni.installHashLib[installPath][h][libName]; !ok {
+					ni.installHashLib[installPath][h][libName] = struct{}{}
 				}
 				if _, ok := ni.hashLibInstall[h]; !ok {
 					ni.hashLibInstall[h] = make(map[string]map[string]struct{})
@@ -197,7 +197,7 @@
 				hl = append(hl, h)
 			}
 			if len(hl) > 0 {
-				sort.Sort(hashList{ni, libName, &hl})
+				sort.Sort(hashList{ni, libName, "", &hl})
 				for _, h := range hl {
 					c <- h
 				}
@@ -230,6 +230,46 @@
 	return installs
 }
 
+// InstallPaths returns the ordered channel of indexed install paths.
+func (ni *NoticeIndex) InstallPaths() chan string {
+	c := make(chan string)
+	go func() {
+		paths := make([]string, 0, len(ni.installHashLib))
+		for path := range ni.installHashLib {
+			paths = append(paths, path)
+		}
+		sort.Strings(paths)
+		for _, installPath := range paths {
+			c <- installPath
+		}
+		close(c)
+	}()
+	return c
+}
+
+// InstallHashes returns the ordered array of hashes attached to `installPath`.
+func (ni *NoticeIndex) InstallHashes(installPath string) []hash {
+	result := make([]hash, 0, len(ni.installHashLib[installPath]))
+	for h := range ni.installHashLib[installPath] {
+		result = append(result, h)
+	}
+	if len(result) > 0 {
+		sort.Sort(hashList{ni, "", installPath, &result})
+	}
+	return result
+}
+
+// InstallHashLibs returns the ordered array of library names attached to
+// `installPath` as hash `h`.
+func (ni *NoticeIndex) InstallHashLibs(installPath string, h hash) []string {
+	result := make([]string, 0, len(ni.installHashLib[installPath][h]))
+	for libName := range ni.installHashLib[installPath][h] {
+		result = append(result, libName)
+	}
+	sort.Strings(result)
+	return result
+}
+
 // HashText returns the file content of the license text hashed as `h`.
 func (ni *NoticeIndex) HashText(h hash) []byte {
 	return ni.text[h]
@@ -492,9 +532,10 @@
 
 // hashList orders an array of hashes
 type hashList struct {
-	ni      *NoticeIndex
-	libName string
-	hashes  *[]hash
+	ni          *NoticeIndex
+	libName     string
+	installPath string
+	hashes      *[]hash
 }
 
 // Len returns the count of elements in the slice.
@@ -511,6 +552,14 @@
 	if 0 < len(l.libName) {
 		insti = len(l.ni.hashLibInstall[(*l.hashes)[i]][l.libName])
 		instj = len(l.ni.hashLibInstall[(*l.hashes)[j]][l.libName])
+	} else {
+		libsi := l.ni.InstallHashLibs(l.installPath, (*l.hashes)[i])
+		libsj := l.ni.InstallHashLibs(l.installPath, (*l.hashes)[j])
+		libsis := strings.Join(libsi, " ")
+		libsjs := strings.Join(libsj, " ")
+		if libsis != libsjs {
+			return libsis < libsjs
+		}
 	}
 	if insti == instj {
 		leni := len(l.ni.text[(*l.hashes)[i]])