blob: ec0160318f888efef02fc48ebcad8304b67d5995 [file] [log] [blame]
Bob Badour2546feb2022-01-26 20:58:24 -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 bill of materials. (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 bill of materials. i.e. the list of installed paths.
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 Badour2546feb2022-01-26 20:58:24 -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 Badour2546feb2022-01-26 20:58:24 -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 := billOfMaterials(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 Badour2546feb2022-01-26 20:58:24 -0800106 os.Exit(1)
107 }
108 }
109 os.Exit(0)
110}
111
112// billOfMaterials implements the bom utility.
113func billOfMaterials(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 path := range ni.InstallPaths() {
137 if 0 < len(ctx.stripPrefix) && strings.HasPrefix(path, ctx.stripPrefix) {
138 fmt.Fprintln(ctx.stdout, path[len(ctx.stripPrefix):])
139 } else {
140 fmt.Fprintln(ctx.stdout, path)
141 }
142 }
143 return nil
144}