aboutsummaryrefslogtreecommitdiff
path: root/compliance
diff options
context:
space:
mode:
authorSasha Smundak <asmundak@google.com>2022-11-09 11:57:00 -0800
committerSasha Smundak <asmundak@google.com>2022-11-10 10:42:32 -0800
commit70f4e497e611f7886c7ffd029b356112bd48f85f (patch)
tree445af51a82fd3d4b78e455694fcb488923dc8cd1 /compliance
parentc57db2d820479418968f0bda38531d03976e96d3 (diff)
downloadbazel-70f4e497e611f7886c7ffd029b356112bd48f85f.tar.gz
Implement license notice generator for Bazel.
Bug: 190817312 Test: treehugger Change-Id: I36521c0a91e5c7e09745407b7ab50f8e91c7296d
Diffstat (limited to 'compliance')
-rw-r--r--compliance/Android.bp24
-rw-r--r--compliance/cmd/bazel_notice_gen/bazel_notice_gen.go174
-rw-r--r--compliance/cmd/bazel_notice_gen/bazel_notice_gen_test.go128
-rw-r--r--compliance/go.mod5
-rw-r--r--compliance/testdata/NOTICE_LICENSE2
5 files changed, 333 insertions, 0 deletions
diff --git a/compliance/Android.bp b/compliance/Android.bp
new file mode 100644
index 00000000..0e709429
--- /dev/null
+++ b/compliance/Android.bp
@@ -0,0 +1,24 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+ name: "bazel_notice_gen",
+ srcs: ["cmd/bazel_notice_gen/bazel_notice_gen.go"],
+ testSrcs: ["cmd/bazel_notice_gen/bazel_notice_gen_test.go"],
+}
diff --git a/compliance/cmd/bazel_notice_gen/bazel_notice_gen.go b/compliance/cmd/bazel_notice_gen/bazel_notice_gen.go
new file mode 100644
index 00000000..75a206cc
--- /dev/null
+++ b/compliance/cmd/bazel_notice_gen/bazel_notice_gen.go
@@ -0,0 +1,174 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "compress/gzip"
+ "crypto/md5"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "html/template"
+ "io"
+ "os"
+ "strings"
+)
+
+var (
+ inputFile string
+ outputFile = flag.String("o", "", "output file")
+ listTargets = flag.Bool("list_targets", false, "list targets using each license")
+)
+
+type LicenseKind struct {
+ Target string `json:"target"`
+ Name string `json:"name"`
+ Conditions []string `json:"conditions"`
+}
+
+type License struct {
+ Rule string `json:"rule"`
+ CopyrightNotice string `json:"copyright_notice"`
+ PackageName string `json:"package_name"`
+ PackageUrl string `json:"package_url"`
+ PackageVersion string `json:"package_version"`
+ LicenseFile string `json:"license_text"`
+ LicenseKinds []LicenseKind `json:"license_kinds"`
+ Licensees []string `json:"licensees"`
+}
+
+type LicenseTextHash string
+
+// generator generates the notices for the given set of licenses read from the JSON-encoded string.
+// As the contents of the license files is often the same, they are read into the map by their hash.
+type generator struct {
+ Licenses []License
+ LicenseTextHash map[string]LicenseTextHash // License.rule->hash of license text contents
+ LicenseTextIndex map[LicenseTextHash]string
+}
+
+func newGenerator(in string) *generator {
+ g := generator{}
+ decoder := json.NewDecoder(strings.NewReader(in))
+ decoder.DisallowUnknownFields() //useful to detect typos, e.g. in unit tests
+ err := decoder.Decode(&g.Licenses)
+ maybeQuit(err)
+ return &g
+}
+
+func (g *generator) buildLicenseTextIndex() {
+ g.LicenseTextHash = make(map[string]LicenseTextHash, len(g.Licenses))
+ g.LicenseTextIndex = make(map[LicenseTextHash]string)
+ for _, l := range g.Licenses {
+ if l.LicenseFile == "" {
+ continue
+ }
+ data, err := os.ReadFile(l.LicenseFile)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%s: bad license file %s: %s\n", l.Rule, l.LicenseFile, err)
+ os.Exit(1)
+ }
+ h := LicenseTextHash(fmt.Sprintf("%x", md5.Sum(data)))
+ g.LicenseTextHash[l.Rule] = h
+ if _, found := g.LicenseTextIndex[h]; !found {
+ g.LicenseTextIndex[h] = string(data)
+ }
+ }
+}
+
+func (g *generator) generate(sink io.Writer, listTargets bool) {
+ const tpl = `<!DOCTYPE html>
+<html>
+ <head>
+ <style type="text/css">
+ body { padding: 2px; margin: 0; }
+ .license { background-color: seashell; margin: 1em;}
+ pre { padding: 1em; }</style></head>
+ <body>
+ The following software has been included in this product and contains the license and notice as shown below.<p>
+ {{- $x := . }}
+ {{- range .Licenses }}
+ {{ if .PackageName }}<strong>{{.PackageName}}</strong>{{- else }}Rule: {{.Rule}}{{ end }}
+ {{- if .CopyrightNotice }}<br>Copyright Notice: {{.CopyrightNotice}}{{ end }}
+ {{- $v := index $x.LicenseTextHash .Rule }}{{- if $v }}<br><a href=#{{$v}}>License</a>{{- end }}<br>
+ {{- if list_targets }}
+ Used by: {{- range .Licensees }} {{.}} {{- end }}<hr>
+ {{- end }}
+ {{- end }}
+ {{ range $k, $v := .LicenseTextIndex }}<div id="{{$k}}" class="license"><pre>{{$v}}
+ </pre></div> {{- end }}
+ </body>
+</html>
+`
+ funcMap := template.FuncMap{
+ "list_targets": func() bool { return listTargets },
+ }
+ t, err := template.New("NoticesPage").Funcs(funcMap).Parse(tpl)
+ maybeQuit(err)
+ if g.LicenseTextHash == nil {
+ g.buildLicenseTextIndex()
+ }
+ maybeQuit(t.Execute(sink, g))
+}
+
+func maybeQuit(err error) {
+ if err == nil {
+ return
+ }
+
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+}
+
+func processArgs() {
+ flag.Usage = func() {
+ fmt.Fprintln(os.Stderr, `usage: bazelhtmlnotice -o <output> <input>`)
+ flag.PrintDefaults()
+ os.Exit(2)
+ }
+ flag.Parse()
+ if len(flag.Args()) != 1 {
+ flag.Usage()
+ }
+ inputFile = flag.Arg(0)
+}
+
+func setupWriting() (io.Writer, io.Closer, *os.File) {
+ if *outputFile == "" {
+ return os.Stdout, nil, nil
+ }
+ ofile, err := os.Create(*outputFile)
+ maybeQuit(err)
+ if !strings.HasSuffix(*outputFile, ".gz") {
+ return ofile, nil, ofile
+ }
+ gz, err := gzip.NewWriterLevel(ofile, gzip.BestCompression)
+ maybeQuit(err)
+ return gz, gz, ofile
+}
+
+func main() {
+ processArgs()
+ data, err := os.ReadFile(inputFile)
+ maybeQuit(err)
+ sink, closer, ofile := setupWriting()
+ newGenerator(string(data)).generate(sink, *listTargets)
+ if closer != nil {
+ maybeQuit(closer.Close())
+ }
+ if ofile != nil {
+ maybeQuit(ofile.Close())
+ }
+}
diff --git a/compliance/cmd/bazel_notice_gen/bazel_notice_gen_test.go b/compliance/cmd/bazel_notice_gen/bazel_notice_gen_test.go
new file mode 100644
index 00000000..31953871
--- /dev/null
+++ b/compliance/cmd/bazel_notice_gen/bazel_notice_gen_test.go
@@ -0,0 +1,128 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bytes"
+ "testing"
+)
+
+func Test_doit(t *testing.T) {
+ input := `
+[
+ {
+ "rule": "@//build/soong/licenses:Android-Apache-2.0",
+ "license_kinds": [
+ {
+ "target": "@//build/soong/licenses:SPDX-license-identifier-Apache-2.0",
+ "name": "SPDX-license-identifier-Apache-2.0",
+ "conditions": ["notice"]
+ }
+ ],
+ "copyright_notice": "Copyright (C) The Android Open Source Project",
+ "package_name": "Discombobulator",
+ "package_url": null,
+ "package_version": null,
+ "license_text": "../../testdata/NOTICE_LICENSE",
+ "licensees": [
+ "@//bionic/libc:libc_bionic_ndk",
+ "@//system/logging/liblog:liblog"
+ ]
+ },
+ {
+ "rule": "@//external/scudo:external_scudo_license",
+ "license_kinds": [
+ {
+ "target": "@//build/soong/licenses:SPDX-license-identifier-Apache-2.0",
+ "name": "SPDX-license-identifier-Apache-2.0",
+ "conditions": ["notice"]
+ }
+ ],
+ "copyright_notice": "",
+ "package_name": "Scudo Standalone",
+ "package_url": null,
+ "package_version": null,
+ "licensees": [
+ "@//external/scudo:foo"
+ ]
+ }
+]
+`
+ tests := []struct {
+ name string
+ in string
+ listTargets bool
+ want string
+ }{
+ {
+ name: "ListTargets",
+ in: input,
+ listTargets: true,
+ want: `<!DOCTYPE html>
+<html>
+ <head>
+ <style type="text/css">
+ body { padding: 2px; margin: 0; }
+ .license { background-color: seashell; margin: 1em;}
+ pre { padding: 1em; }</style></head>
+ <body>
+ The following software has been included in this product and contains the license and notice as shown below.<p>
+ <strong>Discombobulator</strong><br>Copyright Notice: Copyright (C) The Android Open Source Project<br><a href=#b9835e4a000fb18a4c8970690daa3b95>License</a><br>
+ Used by: @//bionic/libc:libc_bionic_ndk @//system/logging/liblog:liblog<hr>
+ <strong>Scudo Standalone</strong><br>
+ Used by: @//external/scudo:foo<hr>
+ <div id="b9835e4a000fb18a4c8970690daa3b95" class="license"><pre>neque porro quisquam est qui do-
+lorem ipsum
+
+ </pre></div>
+ </body>
+</html>
+`,
+ },
+ {
+ name: "NoTargets",
+ in: input,
+ listTargets: false,
+ want: `<!DOCTYPE html>
+<html>
+ <head>
+ <style type="text/css">
+ body { padding: 2px; margin: 0; }
+ .license { background-color: seashell; margin: 1em;}
+ pre { padding: 1em; }</style></head>
+ <body>
+ The following software has been included in this product and contains the license and notice as shown below.<p>
+ <strong>Discombobulator</strong><br>Copyright Notice: Copyright (C) The Android Open Source Project<br><a href=#b9835e4a000fb18a4c8970690daa3b95>License</a><br>
+ <strong>Scudo Standalone</strong><br>
+ <div id="b9835e4a000fb18a4c8970690daa3b95" class="license"><pre>neque porro quisquam est qui do-
+lorem ipsum
+
+ </pre></div>
+ </body>
+</html>
+`,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ buf := bytes.Buffer{}
+ newGenerator(tt.in).generate(&buf, tt.listTargets)
+ got := buf.String()
+ if got != tt.want {
+ t.Errorf("doit() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/compliance/go.mod b/compliance/go.mod
new file mode 100644
index 00000000..336254d2
--- /dev/null
+++ b/compliance/go.mod
@@ -0,0 +1,5 @@
+module android/bazel/compliance
+
+
+
+go 1.19
diff --git a/compliance/testdata/NOTICE_LICENSE b/compliance/testdata/NOTICE_LICENSE
new file mode 100644
index 00000000..da74c170
--- /dev/null
+++ b/compliance/testdata/NOTICE_LICENSE
@@ -0,0 +1,2 @@
+neque porro quisquam est qui do-
+lorem ipsum