blob: 658d6098bf74b8eb3b77000147410eda1d528d41 [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 main
16
17import (
18 "bytes"
19 "compliance"
20 "flag"
21 "fmt"
22 "io"
23 "io/fs"
24 "os"
25 "path/filepath"
26 "strings"
27)
28
29var (
30 outputFile = flag.String("o", "-", "Where to write the NOTICE text file. (default stdout)")
31 stripPrefix = flag.String("strip_prefix", "", "Prefix to remove from paths. i.e. path to root")
32
33 failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
34 failNoLicenses = fmt.Errorf("No licenses found")
35)
36
37type context struct {
38 stdout io.Writer
39 stderr io.Writer
40 rootFS fs.FS
41 stripPrefix string
42}
43
44func init() {
45 flag.Usage = func() {
46 fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
47
48Outputs a text NOTICE file.
49
50Options:
51`, filepath.Base(os.Args[0]))
52 flag.PrintDefaults()
53 }
54}
55
56func main() {
57 flag.Parse()
58
59 // Must specify at least one root target.
60 if flag.NArg() == 0 {
61 flag.Usage()
62 os.Exit(2)
63 }
64
65 if len(*outputFile) == 0 {
66 flag.Usage()
67 fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
68 os.Exit(2)
69 } else {
70 dir, err := filepath.Abs(filepath.Dir(*outputFile))
71 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -080072 fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
Bob Badoure6fdd142021-12-09 22:10:43 -080073 os.Exit(1)
74 }
75 fi, err := os.Stat(dir)
76 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -080077 fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
Bob Badoure6fdd142021-12-09 22:10:43 -080078 os.Exit(1)
79 }
80 if !fi.IsDir() {
81 fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
82 os.Exit(1)
83 }
84 }
85
86 var ofile io.Writer
87 ofile = os.Stdout
88 if *outputFile != "-" {
89 ofile = &bytes.Buffer{}
90 }
91
92 ctx := &context{ofile, os.Stderr, os.DirFS("."), *stripPrefix}
93
94 err := textNotice(ctx, flag.Args()...)
95 if err != nil {
96 if err == failNoneRequested {
97 flag.Usage()
98 }
99 fmt.Fprintf(os.Stderr, "%s\n", err.Error())
100 os.Exit(1)
101 }
102 if *outputFile != "-" {
103 err := os.WriteFile(*outputFile, ofile.(*bytes.Buffer).Bytes(), 0666)
104 if err != nil {
Colin Cross179ec3e2022-01-27 15:47:09 -0800105 fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
Bob Badoure6fdd142021-12-09 22:10:43 -0800106 os.Exit(1)
107 }
108 }
109 os.Exit(0)
110}
111
112// textNotice implements the textNotice utility.
113func textNotice(ctx *context, files ...string) error {
114 // Must be at least one root file.
115 if len(files) < 1 {
116 return failNoneRequested
117 }
118
119 // Read the license graph from the license metadata files (*.meta_lic).
120 licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
121 if err != nil {
122 return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
123 }
124 if licenseGraph == nil {
125 return failNoLicenses
126 }
127
128 // rs contains all notice resolutions.
129 rs := compliance.ResolveNotices(licenseGraph)
130
131 ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
132 if err != nil {
133 return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
134 }
135
136 for h := range ni.Hashes() {
137 fmt.Fprintln(ctx.stdout, "==============================================================================")
138 for _, libName := range ni.HashLibs(h) {
139 fmt.Fprintf(ctx.stdout, "%s used by:\n", libName)
140 for _, installPath := range ni.HashLibInstalls(h, libName) {
141 if 0 < len(ctx.stripPrefix) && strings.HasPrefix(installPath, ctx.stripPrefix) {
142 fmt.Fprintf(ctx.stdout, " %s\n", installPath[len(ctx.stripPrefix):])
143 } else {
144 fmt.Fprintf(ctx.stdout, " %s\n", installPath)
145 }
146 }
147 fmt.Fprintln(ctx.stdout)
148 }
149 ctx.stdout.Write(ni.HashText(h))
150 fmt.Fprintln(ctx.stdout)
151 }
152 return nil
153}