xref: /aosp_15_r20/build/make/tools/compliance/cmd/htmlnotice/htmlnotice.go (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1*9e94795aSAndroid Build Coastguard Worker// Copyright 2021 Google LLC
2*9e94795aSAndroid Build Coastguard Worker//
3*9e94795aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*9e94795aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*9e94795aSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*9e94795aSAndroid Build Coastguard Worker//
7*9e94795aSAndroid Build Coastguard Worker//      http://www.apache.org/licenses/LICENSE-2.0
8*9e94795aSAndroid Build Coastguard Worker//
9*9e94795aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*9e94795aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*9e94795aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*9e94795aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*9e94795aSAndroid Build Coastguard Worker// limitations under the License.
14*9e94795aSAndroid Build Coastguard Worker
15*9e94795aSAndroid Build Coastguard Workerpackage main
16*9e94795aSAndroid Build Coastguard Worker
17*9e94795aSAndroid Build Coastguard Workerimport (
18*9e94795aSAndroid Build Coastguard Worker	"bytes"
19*9e94795aSAndroid Build Coastguard Worker	"compress/gzip"
20*9e94795aSAndroid Build Coastguard Worker	"flag"
21*9e94795aSAndroid Build Coastguard Worker	"fmt"
22*9e94795aSAndroid Build Coastguard Worker	"html"
23*9e94795aSAndroid Build Coastguard Worker	"io"
24*9e94795aSAndroid Build Coastguard Worker	"io/fs"
25*9e94795aSAndroid Build Coastguard Worker	"os"
26*9e94795aSAndroid Build Coastguard Worker	"path/filepath"
27*9e94795aSAndroid Build Coastguard Worker	"sort"
28*9e94795aSAndroid Build Coastguard Worker	"strings"
29*9e94795aSAndroid Build Coastguard Worker
30*9e94795aSAndroid Build Coastguard Worker	"android/soong/response"
31*9e94795aSAndroid Build Coastguard Worker	"android/soong/tools/compliance"
32*9e94795aSAndroid Build Coastguard Worker
33*9e94795aSAndroid Build Coastguard Worker	"github.com/google/blueprint/deptools"
34*9e94795aSAndroid Build Coastguard Worker)
35*9e94795aSAndroid Build Coastguard Worker
36*9e94795aSAndroid Build Coastguard Workervar (
37*9e94795aSAndroid Build Coastguard Worker	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
38*9e94795aSAndroid Build Coastguard Worker	failNoLicenses    = fmt.Errorf("No licenses found")
39*9e94795aSAndroid Build Coastguard Worker)
40*9e94795aSAndroid Build Coastguard Worker
41*9e94795aSAndroid Build Coastguard Workertype context struct {
42*9e94795aSAndroid Build Coastguard Worker	stdout      io.Writer
43*9e94795aSAndroid Build Coastguard Worker	stderr      io.Writer
44*9e94795aSAndroid Build Coastguard Worker	rootFS      fs.FS
45*9e94795aSAndroid Build Coastguard Worker	includeTOC  bool
46*9e94795aSAndroid Build Coastguard Worker	product     string
47*9e94795aSAndroid Build Coastguard Worker	stripPrefix []string
48*9e94795aSAndroid Build Coastguard Worker	title       string
49*9e94795aSAndroid Build Coastguard Worker	deps        *[]string
50*9e94795aSAndroid Build Coastguard Worker}
51*9e94795aSAndroid Build Coastguard Worker
52*9e94795aSAndroid Build Coastguard Workerfunc (ctx context) strip(installPath string) string {
53*9e94795aSAndroid Build Coastguard Worker	for _, prefix := range ctx.stripPrefix {
54*9e94795aSAndroid Build Coastguard Worker		if strings.HasPrefix(installPath, prefix) {
55*9e94795aSAndroid Build Coastguard Worker			p := strings.TrimPrefix(installPath, prefix)
56*9e94795aSAndroid Build Coastguard Worker			if 0 == len(p) {
57*9e94795aSAndroid Build Coastguard Worker				p = ctx.product
58*9e94795aSAndroid Build Coastguard Worker			}
59*9e94795aSAndroid Build Coastguard Worker			if 0 == len(p) {
60*9e94795aSAndroid Build Coastguard Worker				continue
61*9e94795aSAndroid Build Coastguard Worker			}
62*9e94795aSAndroid Build Coastguard Worker			return p
63*9e94795aSAndroid Build Coastguard Worker		}
64*9e94795aSAndroid Build Coastguard Worker	}
65*9e94795aSAndroid Build Coastguard Worker	return installPath
66*9e94795aSAndroid Build Coastguard Worker}
67*9e94795aSAndroid Build Coastguard Worker
68*9e94795aSAndroid Build Coastguard Worker// newMultiString creates a flag that allows multiple values in an array.
69*9e94795aSAndroid Build Coastguard Workerfunc newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
70*9e94795aSAndroid Build Coastguard Worker	var f multiString
71*9e94795aSAndroid Build Coastguard Worker	flags.Var(&f, name, usage)
72*9e94795aSAndroid Build Coastguard Worker	return &f
73*9e94795aSAndroid Build Coastguard Worker}
74*9e94795aSAndroid Build Coastguard Worker
75*9e94795aSAndroid Build Coastguard Worker// multiString implements the flag `Value` interface for multiple strings.
76*9e94795aSAndroid Build Coastguard Workertype multiString []string
77*9e94795aSAndroid Build Coastguard Worker
78*9e94795aSAndroid Build Coastguard Workerfunc (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
79*9e94795aSAndroid Build Coastguard Workerfunc (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
80*9e94795aSAndroid Build Coastguard Worker
81*9e94795aSAndroid Build Coastguard Workerfunc main() {
82*9e94795aSAndroid Build Coastguard Worker	var expandedArgs []string
83*9e94795aSAndroid Build Coastguard Worker	for _, arg := range os.Args[1:] {
84*9e94795aSAndroid Build Coastguard Worker		if strings.HasPrefix(arg, "@") {
85*9e94795aSAndroid Build Coastguard Worker			f, err := os.Open(strings.TrimPrefix(arg, "@"))
86*9e94795aSAndroid Build Coastguard Worker			if err != nil {
87*9e94795aSAndroid Build Coastguard Worker				fmt.Fprintln(os.Stderr, err.Error())
88*9e94795aSAndroid Build Coastguard Worker				os.Exit(1)
89*9e94795aSAndroid Build Coastguard Worker			}
90*9e94795aSAndroid Build Coastguard Worker
91*9e94795aSAndroid Build Coastguard Worker			respArgs, err := response.ReadRspFile(f)
92*9e94795aSAndroid Build Coastguard Worker			f.Close()
93*9e94795aSAndroid Build Coastguard Worker			if err != nil {
94*9e94795aSAndroid Build Coastguard Worker				fmt.Fprintln(os.Stderr, err.Error())
95*9e94795aSAndroid Build Coastguard Worker				os.Exit(1)
96*9e94795aSAndroid Build Coastguard Worker			}
97*9e94795aSAndroid Build Coastguard Worker			expandedArgs = append(expandedArgs, respArgs...)
98*9e94795aSAndroid Build Coastguard Worker		} else {
99*9e94795aSAndroid Build Coastguard Worker			expandedArgs = append(expandedArgs, arg)
100*9e94795aSAndroid Build Coastguard Worker		}
101*9e94795aSAndroid Build Coastguard Worker	}
102*9e94795aSAndroid Build Coastguard Worker
103*9e94795aSAndroid Build Coastguard Worker	flags := flag.NewFlagSet("flags", flag.ExitOnError)
104*9e94795aSAndroid Build Coastguard Worker
105*9e94795aSAndroid Build Coastguard Worker	flags.Usage = func() {
106*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
107*9e94795aSAndroid Build Coastguard Worker
108*9e94795aSAndroid Build Coastguard WorkerOutputs an html NOTICE.html or gzipped NOTICE.html.gz file if the -o filename
109*9e94795aSAndroid Build Coastguard Workerends with ".gz".
110*9e94795aSAndroid Build Coastguard Worker
111*9e94795aSAndroid Build Coastguard WorkerOptions:
112*9e94795aSAndroid Build Coastguard Worker`, filepath.Base(os.Args[0]))
113*9e94795aSAndroid Build Coastguard Worker		flags.PrintDefaults()
114*9e94795aSAndroid Build Coastguard Worker	}
115*9e94795aSAndroid Build Coastguard Worker
116*9e94795aSAndroid Build Coastguard Worker	outputFile := flags.String("o", "-", "Where to write the NOTICE text file. (default stdout)")
117*9e94795aSAndroid Build Coastguard Worker	depsFile := flags.String("d", "", "Where to write the deps file")
118*9e94795aSAndroid Build Coastguard Worker	includeTOC := flags.Bool("toc", true, "Whether to include a table of contents.")
119*9e94795aSAndroid Build Coastguard Worker	product := flags.String("product", "", "The name of the product for which the notice is generated.")
120*9e94795aSAndroid Build Coastguard Worker	stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
121*9e94795aSAndroid Build Coastguard Worker	title := flags.String("title", "", "The title of the notice file.")
122*9e94795aSAndroid Build Coastguard Worker
123*9e94795aSAndroid Build Coastguard Worker	flags.Parse(expandedArgs)
124*9e94795aSAndroid Build Coastguard Worker
125*9e94795aSAndroid Build Coastguard Worker	// Must specify at least one root target.
126*9e94795aSAndroid Build Coastguard Worker	if flags.NArg() == 0 {
127*9e94795aSAndroid Build Coastguard Worker		flags.Usage()
128*9e94795aSAndroid Build Coastguard Worker		os.Exit(2)
129*9e94795aSAndroid Build Coastguard Worker	}
130*9e94795aSAndroid Build Coastguard Worker
131*9e94795aSAndroid Build Coastguard Worker	if len(*outputFile) == 0 {
132*9e94795aSAndroid Build Coastguard Worker		flags.Usage()
133*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
134*9e94795aSAndroid Build Coastguard Worker		os.Exit(2)
135*9e94795aSAndroid Build Coastguard Worker	} else {
136*9e94795aSAndroid Build Coastguard Worker		dir, err := filepath.Abs(filepath.Dir(*outputFile))
137*9e94795aSAndroid Build Coastguard Worker		if err != nil {
138*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
139*9e94795aSAndroid Build Coastguard Worker			os.Exit(1)
140*9e94795aSAndroid Build Coastguard Worker		}
141*9e94795aSAndroid Build Coastguard Worker		fi, err := os.Stat(dir)
142*9e94795aSAndroid Build Coastguard Worker		if err != nil {
143*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
144*9e94795aSAndroid Build Coastguard Worker			os.Exit(1)
145*9e94795aSAndroid Build Coastguard Worker		}
146*9e94795aSAndroid Build Coastguard Worker		if !fi.IsDir() {
147*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
148*9e94795aSAndroid Build Coastguard Worker			os.Exit(1)
149*9e94795aSAndroid Build Coastguard Worker		}
150*9e94795aSAndroid Build Coastguard Worker	}
151*9e94795aSAndroid Build Coastguard Worker
152*9e94795aSAndroid Build Coastguard Worker	var ofile io.Writer
153*9e94795aSAndroid Build Coastguard Worker	var closer io.Closer
154*9e94795aSAndroid Build Coastguard Worker	ofile = os.Stdout
155*9e94795aSAndroid Build Coastguard Worker	var obuf *bytes.Buffer
156*9e94795aSAndroid Build Coastguard Worker	if *outputFile != "-" {
157*9e94795aSAndroid Build Coastguard Worker		obuf = &bytes.Buffer{}
158*9e94795aSAndroid Build Coastguard Worker		ofile = obuf
159*9e94795aSAndroid Build Coastguard Worker	}
160*9e94795aSAndroid Build Coastguard Worker	if strings.HasSuffix(*outputFile, ".gz") {
161*9e94795aSAndroid Build Coastguard Worker		ofile, _ = gzip.NewWriterLevel(obuf, gzip.BestCompression)
162*9e94795aSAndroid Build Coastguard Worker		closer = ofile.(io.Closer)
163*9e94795aSAndroid Build Coastguard Worker	}
164*9e94795aSAndroid Build Coastguard Worker
165*9e94795aSAndroid Build Coastguard Worker	var deps []string
166*9e94795aSAndroid Build Coastguard Worker
167*9e94795aSAndroid Build Coastguard Worker	ctx := &context{ofile, os.Stderr, compliance.FS, *includeTOC, *product, *stripPrefix, *title, &deps}
168*9e94795aSAndroid Build Coastguard Worker
169*9e94795aSAndroid Build Coastguard Worker	err := htmlNotice(ctx, flags.Args()...)
170*9e94795aSAndroid Build Coastguard Worker	if err != nil {
171*9e94795aSAndroid Build Coastguard Worker		if err == failNoneRequested {
172*9e94795aSAndroid Build Coastguard Worker			flags.Usage()
173*9e94795aSAndroid Build Coastguard Worker		}
174*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
175*9e94795aSAndroid Build Coastguard Worker		os.Exit(1)
176*9e94795aSAndroid Build Coastguard Worker	}
177*9e94795aSAndroid Build Coastguard Worker	if closer != nil {
178*9e94795aSAndroid Build Coastguard Worker		closer.Close()
179*9e94795aSAndroid Build Coastguard Worker	}
180*9e94795aSAndroid Build Coastguard Worker
181*9e94795aSAndroid Build Coastguard Worker	if *outputFile != "-" {
182*9e94795aSAndroid Build Coastguard Worker		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
183*9e94795aSAndroid Build Coastguard Worker		if err != nil {
184*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
185*9e94795aSAndroid Build Coastguard Worker			os.Exit(1)
186*9e94795aSAndroid Build Coastguard Worker		}
187*9e94795aSAndroid Build Coastguard Worker	}
188*9e94795aSAndroid Build Coastguard Worker	if *depsFile != "" {
189*9e94795aSAndroid Build Coastguard Worker		err := deptools.WriteDepFile(*depsFile, *outputFile, deps)
190*9e94795aSAndroid Build Coastguard Worker		if err != nil {
191*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(os.Stderr, "could not write deps to %q: %s\n", *depsFile, err)
192*9e94795aSAndroid Build Coastguard Worker			os.Exit(1)
193*9e94795aSAndroid Build Coastguard Worker		}
194*9e94795aSAndroid Build Coastguard Worker	}
195*9e94795aSAndroid Build Coastguard Worker	os.Exit(0)
196*9e94795aSAndroid Build Coastguard Worker}
197*9e94795aSAndroid Build Coastguard Worker
198*9e94795aSAndroid Build Coastguard Worker// htmlNotice implements the htmlnotice utility.
199*9e94795aSAndroid Build Coastguard Workerfunc htmlNotice(ctx *context, files ...string) error {
200*9e94795aSAndroid Build Coastguard Worker	// Must be at least one root file.
201*9e94795aSAndroid Build Coastguard Worker	if len(files) < 1 {
202*9e94795aSAndroid Build Coastguard Worker		return failNoneRequested
203*9e94795aSAndroid Build Coastguard Worker	}
204*9e94795aSAndroid Build Coastguard Worker
205*9e94795aSAndroid Build Coastguard Worker	// Read the license graph from the license metadata files (*.meta_lic).
206*9e94795aSAndroid Build Coastguard Worker	licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
207*9e94795aSAndroid Build Coastguard Worker	if err != nil {
208*9e94795aSAndroid Build Coastguard Worker		return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
209*9e94795aSAndroid Build Coastguard Worker	}
210*9e94795aSAndroid Build Coastguard Worker	if licenseGraph == nil {
211*9e94795aSAndroid Build Coastguard Worker		return failNoLicenses
212*9e94795aSAndroid Build Coastguard Worker	}
213*9e94795aSAndroid Build Coastguard Worker
214*9e94795aSAndroid Build Coastguard Worker	// rs contains all notice resolutions.
215*9e94795aSAndroid Build Coastguard Worker	rs := compliance.ResolveNotices(licenseGraph)
216*9e94795aSAndroid Build Coastguard Worker
217*9e94795aSAndroid Build Coastguard Worker	ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
218*9e94795aSAndroid Build Coastguard Worker	if err != nil {
219*9e94795aSAndroid Build Coastguard Worker		return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
220*9e94795aSAndroid Build Coastguard Worker	}
221*9e94795aSAndroid Build Coastguard Worker
222*9e94795aSAndroid Build Coastguard Worker	fmt.Fprintln(ctx.stdout, "<!DOCTYPE html>")
223*9e94795aSAndroid Build Coastguard Worker	fmt.Fprintln(ctx.stdout, "<html><head>")
224*9e94795aSAndroid Build Coastguard Worker	fmt.Fprintln(ctx.stdout, "<style type=\"text/css\">")
225*9e94795aSAndroid Build Coastguard Worker	fmt.Fprintln(ctx.stdout, "body { padding: 2px; margin: 0; }")
226*9e94795aSAndroid Build Coastguard Worker	fmt.Fprintln(ctx.stdout, "ul { list-style-type: none; margin: 0; padding: 0; }")
227*9e94795aSAndroid Build Coastguard Worker	fmt.Fprintln(ctx.stdout, "li { padding-left: 1em; }")
228*9e94795aSAndroid Build Coastguard Worker	fmt.Fprintln(ctx.stdout, ".file-list { margin-left: 1em; }")
229*9e94795aSAndroid Build Coastguard Worker	fmt.Fprintln(ctx.stdout, "</style>")
230*9e94795aSAndroid Build Coastguard Worker	if len(ctx.title) > 0 {
231*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(ctx.stdout, "<title>%s</title>\n", html.EscapeString(ctx.title))
232*9e94795aSAndroid Build Coastguard Worker	} else if len(ctx.product) > 0 {
233*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(ctx.stdout, "<title>%s</title>\n", html.EscapeString(ctx.product))
234*9e94795aSAndroid Build Coastguard Worker	}
235*9e94795aSAndroid Build Coastguard Worker	fmt.Fprintln(ctx.stdout, "</head>")
236*9e94795aSAndroid Build Coastguard Worker	fmt.Fprintln(ctx.stdout, "<body>")
237*9e94795aSAndroid Build Coastguard Worker
238*9e94795aSAndroid Build Coastguard Worker	if len(ctx.title) > 0 {
239*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(ctx.stdout, "  <h1>%s</h1>\n", html.EscapeString(ctx.title))
240*9e94795aSAndroid Build Coastguard Worker	} else if len(ctx.product) > 0 {
241*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(ctx.stdout, "  <h1>%s</h1>\n", html.EscapeString(ctx.product))
242*9e94795aSAndroid Build Coastguard Worker	}
243*9e94795aSAndroid Build Coastguard Worker	ids := make(map[string]string)
244*9e94795aSAndroid Build Coastguard Worker	if ctx.includeTOC {
245*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintln(ctx.stdout, "  <ul class=\"toc\">")
246*9e94795aSAndroid Build Coastguard Worker		i := 0
247*9e94795aSAndroid Build Coastguard Worker		for installPath := range ni.InstallPaths() {
248*9e94795aSAndroid Build Coastguard Worker			id := fmt.Sprintf("id%d", i)
249*9e94795aSAndroid Build Coastguard Worker			i++
250*9e94795aSAndroid Build Coastguard Worker			ids[installPath] = id
251*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(ctx.stdout, "    <li id=\"%s\"><strong>%s</strong>\n      <ul>\n", id, html.EscapeString(ctx.strip(installPath)))
252*9e94795aSAndroid Build Coastguard Worker			for _, h := range ni.InstallHashes(installPath) {
253*9e94795aSAndroid Build Coastguard Worker				libs := ni.InstallHashLibs(installPath, h)
254*9e94795aSAndroid Build Coastguard Worker				fmt.Fprintf(ctx.stdout, "        <li><a href=\"#%s\">%s</a>\n", h.String(), html.EscapeString(strings.Join(libs, ", ")))
255*9e94795aSAndroid Build Coastguard Worker			}
256*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintln(ctx.stdout, "      </ul>")
257*9e94795aSAndroid Build Coastguard Worker		}
258*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintln(ctx.stdout, "  </ul><!-- toc -->")
259*9e94795aSAndroid Build Coastguard Worker	}
260*9e94795aSAndroid Build Coastguard Worker	for h := range ni.Hashes() {
261*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintln(ctx.stdout, "  <hr>")
262*9e94795aSAndroid Build Coastguard Worker		for _, libName := range ni.HashLibs(h) {
263*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(ctx.stdout, "  <strong>%s</strong> used by:\n    <ul class=\"file-list\">\n", html.EscapeString(libName))
264*9e94795aSAndroid Build Coastguard Worker			for _, installPath := range ni.HashLibInstalls(h, libName) {
265*9e94795aSAndroid Build Coastguard Worker				if id, ok := ids[installPath]; ok {
266*9e94795aSAndroid Build Coastguard Worker					fmt.Fprintf(ctx.stdout, "      <li><a href=\"#%s\">%s</a>\n", id, html.EscapeString(ctx.strip(installPath)))
267*9e94795aSAndroid Build Coastguard Worker				} else {
268*9e94795aSAndroid Build Coastguard Worker					fmt.Fprintf(ctx.stdout, "      <li>%s\n", html.EscapeString(ctx.strip(installPath)))
269*9e94795aSAndroid Build Coastguard Worker				}
270*9e94795aSAndroid Build Coastguard Worker			}
271*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintf(ctx.stdout, "    </ul>\n")
272*9e94795aSAndroid Build Coastguard Worker		}
273*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintf(ctx.stdout, "  </ul>\n  <a id=\"%s\"/><pre class=\"license-text\">", h.String())
274*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintln(ctx.stdout, html.EscapeString(string(ni.HashText(h))))
275*9e94795aSAndroid Build Coastguard Worker		fmt.Fprintln(ctx.stdout, "  </pre><!-- license-text -->")
276*9e94795aSAndroid Build Coastguard Worker	}
277*9e94795aSAndroid Build Coastguard Worker	fmt.Fprintln(ctx.stdout, "</body></html>")
278*9e94795aSAndroid Build Coastguard Worker
279*9e94795aSAndroid Build Coastguard Worker	*ctx.deps = ni.InputFiles()
280*9e94795aSAndroid Build Coastguard Worker	sort.Strings(*ctx.deps)
281*9e94795aSAndroid Build Coastguard Worker
282*9e94795aSAndroid Build Coastguard Worker	return nil
283*9e94795aSAndroid Build Coastguard Worker}
284