From 986a83916150a3fb259daa003947b4f400183295 Mon Sep 17 00:00:00 2001 From: Bob Badour Date: Fri, 3 Jun 2022 10:42:27 -0700 Subject: [PATCH] Improve flags for compliance tools. Test: m droid dist reportmissinglicenses Change-Id: I4090dae3d5d33d1908d67dff31aeee92d2b261da --- tools/compliance/Android.bp | 33 ++++- tools/compliance/cmd/bom/bom.go | 68 ++++++---- tools/compliance/cmd/checkshare/checkshare.go | 117 +++++++++++++---- tools/compliance/cmd/dumpgraph/dumpgraph.go | 112 ++++++++++++---- .../cmd/dumpresolutions/dumpresolutions.go | 114 ++++++++++++---- tools/compliance/cmd/htmlnotice/htmlnotice.go | 78 +++++++---- tools/compliance/cmd/listshare/listshare.go | 91 ++++++++++--- tools/compliance/cmd/rtrace/rtrace.go | 122 +++++++++++++----- .../compliance/cmd/shippedlibs/shippedlibs.go | 3 - tools/compliance/cmd/textnotice/textnotice.go | 74 +++++++---- tools/compliance/cmd/xmlnotice/xmlnotice.go | 76 +++++++---- 11 files changed, 649 insertions(+), 239 deletions(-) diff --git a/tools/compliance/Android.bp b/tools/compliance/Android.bp index 7a6c4ba4c0..225f3a578b 100644 --- a/tools/compliance/Android.bp +++ b/tools/compliance/Android.bp @@ -20,14 +20,20 @@ package { blueprint_go_binary { name: "compliance_checkshare", srcs: ["cmd/checkshare/checkshare.go"], - deps: ["compliance-module"], + deps: [ + "compliance-module", + "soong-response", + ], testSrcs: ["cmd/checkshare/checkshare_test.go"], } blueprint_go_binary { name: "compliancenotice_bom", srcs: ["cmd/bom/bom.go"], - deps: ["compliance-module"], + deps: [ + "compliance-module", + "soong-response", + ], testSrcs: ["cmd/bom/bom_test.go"], } @@ -44,21 +50,30 @@ blueprint_go_binary { blueprint_go_binary { name: "compliance_listshare", srcs: ["cmd/listshare/listshare.go"], - deps: ["compliance-module"], + deps: [ + "compliance-module", + "soong-response", + ], testSrcs: ["cmd/listshare/listshare_test.go"], } blueprint_go_binary { name: "compliance_dumpgraph", srcs: ["cmd/dumpgraph/dumpgraph.go"], - deps: ["compliance-module"], + deps: [ + "compliance-module", + "soong-response", + ], testSrcs: ["cmd/dumpgraph/dumpgraph_test.go"], } blueprint_go_binary { name: "compliance_dumpresolutions", srcs: ["cmd/dumpresolutions/dumpresolutions.go"], - deps: ["compliance-module"], + deps: [ + "compliance-module", + "soong-response", + ], testSrcs: ["cmd/dumpresolutions/dumpresolutions_test.go"], } @@ -68,6 +83,7 @@ blueprint_go_binary { deps: [ "compliance-module", "blueprint-deptools", + "soong-response", ], testSrcs: ["cmd/htmlnotice/htmlnotice_test.go"], } @@ -75,7 +91,10 @@ blueprint_go_binary { blueprint_go_binary { name: "compliance_rtrace", srcs: ["cmd/rtrace/rtrace.go"], - deps: ["compliance-module"], + deps: [ + "compliance-module", + "soong-response", + ], testSrcs: ["cmd/rtrace/rtrace_test.go"], } @@ -85,6 +104,7 @@ blueprint_go_binary { deps: [ "compliance-module", "blueprint-deptools", + "soong-response", ], testSrcs: ["cmd/textnotice/textnotice_test.go"], } @@ -95,6 +115,7 @@ blueprint_go_binary { deps: [ "compliance-module", "blueprint-deptools", + "soong-response", ], testSrcs: ["cmd/xmlnotice/xmlnotice_test.go"], } diff --git a/tools/compliance/cmd/bom/bom.go b/tools/compliance/cmd/bom/bom.go index b613a1f9ff..187f828057 100644 --- a/tools/compliance/cmd/bom/bom.go +++ b/tools/compliance/cmd/bom/bom.go @@ -24,13 +24,11 @@ import ( "path/filepath" "strings" + "android/soong/response" "android/soong/tools/compliance" ) var ( - outputFile = flag.String("o", "-", "Where to write the bill of materials. (default stdout)") - stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") - failNoneRequested = fmt.Errorf("\nNo license metadata files requested") failNoLicenses = fmt.Errorf("No licenses found") ) @@ -55,22 +53,10 @@ func (ctx context) strip(installPath string) string { return installPath } -func init() { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} - -Outputs a bill of materials. i.e. the list of installed paths. - -Options: -`, filepath.Base(os.Args[0])) - flag.PrintDefaults() - } -} - // newMultiString creates a flag that allows multiple values in an array. -func newMultiString(name, usage string) *multiString { +func newMultiString(flags *flag.FlagSet, name, usage string) *multiString { var f multiString - flag.Var(&f, name, usage) + flags.Var(&f, name, usage) return &f } @@ -81,16 +67,52 @@ func (ms *multiString) String() string { return strings.Join(*ms, ", ") } func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } func main() { - flag.Parse() + var expandedArgs []string + for _, arg := range os.Args[1:] { + if strings.HasPrefix(arg, "@") { + f, err := os.Open(strings.TrimPrefix(arg, "@")) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + respArgs, err := response.ReadRspFile(f) + f.Close() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + expandedArgs = append(expandedArgs, respArgs...) + } else { + expandedArgs = append(expandedArgs, arg) + } + } + + flags := flag.NewFlagSet("flags", flag.ExitOnError) + + flags.Usage = func() { + fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} + +Outputs a bill of materials. i.e. the list of installed paths. + +Options: +`, filepath.Base(os.Args[0])) + flags.PrintDefaults() + } + + outputFile := flags.String("o", "-", "Where to write the bill of materials. (default stdout)") + stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") + + flags.Parse(expandedArgs) // Must specify at least one root target. - if flag.NArg() == 0 { - flag.Usage() + if flags.NArg() == 0 { + flags.Usage() os.Exit(2) } if len(*outputFile) == 0 { - flag.Usage() + flags.Usage() fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") os.Exit(2) } else { @@ -118,10 +140,10 @@ func main() { ctx := &context{ofile, os.Stderr, compliance.FS, *stripPrefix} - err := billOfMaterials(ctx, flag.Args()...) + err := billOfMaterials(ctx, flags.Args()...) if err != nil { if err == failNoneRequested { - flag.Usage() + flags.Usage() } fmt.Fprintf(os.Stderr, "%s\n", err.Error()) os.Exit(1) diff --git a/tools/compliance/cmd/checkshare/checkshare.go b/tools/compliance/cmd/checkshare/checkshare.go index 73bdcb5679..f7b4cd2e39 100644 --- a/tools/compliance/cmd/checkshare/checkshare.go +++ b/tools/compliance/cmd/checkshare/checkshare.go @@ -15,6 +15,7 @@ package main import ( + "bytes" "flag" "fmt" "io" @@ -22,31 +23,12 @@ import ( "os" "path/filepath" "sort" + "strings" + "android/soong/response" "android/soong/tools/compliance" ) -func init() { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, `Usage: %s file.meta_lic {file.meta_lic...} - -Reports on stderr any targets where policy says that the source both -must and must not be shared. The error report indicates the target, the -license condition that has a source privacy policy, and the license -condition that has a source sharing policy. - -Any given target may appear multiple times with different combinations -of conflicting license conditions. - -If all the source code that policy says must be shared may be shared, -outputs "PASS" to stdout and exits with status 0. - -If policy says any source must both be shared and not be shared, -outputs "FAIL" to stdout and exits with status 1. -`, filepath.Base(os.Args[0])) - } -} - var ( failConflicts = fmt.Errorf("conflicts") failNoneRequested = fmt.Errorf("\nNo metadata files requested") @@ -61,24 +43,105 @@ func (l byError) Swap(i, j int) { l[i], l[j] = l[j], l[i] } func (l byError) Less(i, j int) bool { return l[i].Error() < l[j].Error() } func main() { - flag.Parse() + var expandedArgs []string + for _, arg := range os.Args[1:] { + if strings.HasPrefix(arg, "@") { + f, err := os.Open(strings.TrimPrefix(arg, "@")) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + respArgs, err := response.ReadRspFile(f) + f.Close() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + expandedArgs = append(expandedArgs, respArgs...) + } else { + expandedArgs = append(expandedArgs, arg) + } + } + + flags := flag.NewFlagSet("flags", flag.ExitOnError) + + flags.Usage = func() { + fmt.Fprintf(os.Stderr, `Usage: %s {-o outfile} file.meta_lic {file.meta_lic...} + +Reports on stderr any targets where policy says that the source both +must and must not be shared. The error report indicates the target, the +license condition that has a source privacy policy, and the license +condition that has a source sharing policy. + +Any given target may appear multiple times with different combinations +of conflicting license conditions. + +If all the source code that policy says must be shared may be shared, +outputs "PASS" to stdout and exits with status 0. + +If policy says any source must both be shared and not be shared, +outputs "FAIL" to stdout and exits with status 1. +`, filepath.Base(os.Args[0])) + flags.PrintDefaults() + } + + outputFile := flags.String("o", "-", "Where to write the output. (default stdout)") + + flags.Parse(expandedArgs) // Must specify at least one root target. - if flag.NArg() == 0 { - flag.Usage() + if flags.NArg() == 0 { + flags.Usage() os.Exit(2) } - err := checkShare(os.Stdout, os.Stderr, compliance.FS, flag.Args()...) + if len(*outputFile) == 0 { + flags.Usage() + fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") + os.Exit(2) + } else { + dir, err := filepath.Abs(filepath.Dir(*outputFile)) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err) + os.Exit(1) + } + fi, err := os.Stat(dir) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err) + os.Exit(1) + } + if !fi.IsDir() { + fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile) + os.Exit(1) + } + } + + var ofile io.Writer + ofile = os.Stdout + var obuf *bytes.Buffer + if *outputFile != "-" { + obuf = &bytes.Buffer{} + ofile = obuf + } + + err := checkShare(ofile, os.Stderr, compliance.FS, flags.Args()...) if err != nil { if err != failConflicts { if err == failNoneRequested { - flag.Usage() + flags.Usage() } fmt.Fprintf(os.Stderr, "%s\n", err.Error()) } os.Exit(1) } + if *outputFile != "-" { + err := os.WriteFile(*outputFile, obuf.Bytes(), 0666) + if err != nil { + fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err) + os.Exit(1) + } + } os.Exit(0) } @@ -92,7 +155,7 @@ func checkShare(stdout, stderr io.Writer, rootFS fs.FS, files ...string) error { // Read the license graph from the license metadata files (*.meta_lic). licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files) if err != nil { - return fmt.Errorf("Unable to read license metadata file(s) %q: %w\n", files, err) + return fmt.Errorf("Unable to read license metadata file(s) %q from %q: %w\n", files, os.Getenv("PWD"), err) } if licenseGraph == nil { return failNoLicenses diff --git a/tools/compliance/cmd/dumpgraph/dumpgraph.go b/tools/compliance/cmd/dumpgraph/dumpgraph.go index 32a3fc44cf..56257794a4 100644 --- a/tools/compliance/cmd/dumpgraph/dumpgraph.go +++ b/tools/compliance/cmd/dumpgraph/dumpgraph.go @@ -15,6 +15,7 @@ package main import ( + "bytes" "flag" "fmt" "io" @@ -24,14 +25,11 @@ import ( "sort" "strings" + "android/soong/response" "android/soong/tools/compliance" ) var ( - graphViz = flag.Bool("dot", false, "Whether to output graphviz (i.e. dot) format.") - labelConditions = flag.Bool("label_conditions", false, "Whether to label target nodes with conditions.") - stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") - failNoneRequested = fmt.Errorf("\nNo license metadata files requested") failNoLicenses = fmt.Errorf("No licenses found") ) @@ -55,8 +53,44 @@ func (ctx context) strip(installPath string) string { return installPath } -func init() { - flag.Usage = func() { +// newMultiString creates a flag that allows multiple values in an array. +func newMultiString(flags *flag.FlagSet, name, usage string) *multiString { + var f multiString + flags.Var(&f, name, usage) + return &f +} + +// multiString implements the flag `Value` interface for multiple strings. +type multiString []string + +func (ms *multiString) String() string { return strings.Join(*ms, ", ") } +func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } + +func main() { + var expandedArgs []string + for _, arg := range os.Args[1:] { + if strings.HasPrefix(arg, "@") { + f, err := os.Open(strings.TrimPrefix(arg, "@")) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + respArgs, err := response.ReadRspFile(f) + f.Close() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + expandedArgs = append(expandedArgs, respArgs...) + } else { + expandedArgs = append(expandedArgs, arg) + } + } + + flags := flag.NewFlagSet("flags", flag.ExitOnError) + + flags.Usage = func() { fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} Outputs space-separated Target Dependency Annotations tuples for each @@ -70,42 +104,68 @@ target:condition1:condition2 etc. Options: `, filepath.Base(os.Args[0])) - flag.PrintDefaults() + flags.PrintDefaults() } -} -// newMultiString creates a flag that allows multiple values in an array. -func newMultiString(name, usage string) *multiString { - var f multiString - flag.Var(&f, name, usage) - return &f -} + graphViz := flags.Bool("dot", false, "Whether to output graphviz (i.e. dot) format.") + labelConditions := flags.Bool("label_conditions", false, "Whether to label target nodes with conditions.") + outputFile := flags.String("o", "-", "Where to write the output. (default stdout)") + stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") -// multiString implements the flag `Value` interface for multiple strings. -type multiString []string - -func (ms *multiString) String() string { return strings.Join(*ms, ", ") } -func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } - -func main() { - flag.Parse() + flags.Parse(expandedArgs) // Must specify at least one root target. - if flag.NArg() == 0 { - flag.Usage() + if flags.NArg() == 0 { + flags.Usage() os.Exit(2) } + if len(*outputFile) == 0 { + flags.Usage() + fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") + os.Exit(2) + } else { + dir, err := filepath.Abs(filepath.Dir(*outputFile)) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err) + os.Exit(1) + } + fi, err := os.Stat(dir) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err) + os.Exit(1) + } + if !fi.IsDir() { + fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile) + os.Exit(1) + } + } + + var ofile io.Writer + ofile = os.Stdout + var obuf *bytes.Buffer + if *outputFile != "-" { + obuf = &bytes.Buffer{} + ofile = obuf + } + ctx := &context{*graphViz, *labelConditions, *stripPrefix} - err := dumpGraph(ctx, os.Stdout, os.Stderr, compliance.FS, flag.Args()...) + err := dumpGraph(ctx, ofile, os.Stderr, compliance.FS, flags.Args()...) if err != nil { if err == failNoneRequested { - flag.Usage() + flags.Usage() } fmt.Fprintf(os.Stderr, "%s\n", err.Error()) os.Exit(1) } + if *outputFile != "-" { + err := os.WriteFile(*outputFile, obuf.Bytes(), 0666) + if err != nil { + fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err) + os.Exit(1) + } + } os.Exit(0) } diff --git a/tools/compliance/cmd/dumpresolutions/dumpresolutions.go b/tools/compliance/cmd/dumpresolutions/dumpresolutions.go index d02c238846..dc0cf884f5 100644 --- a/tools/compliance/cmd/dumpresolutions/dumpresolutions.go +++ b/tools/compliance/cmd/dumpresolutions/dumpresolutions.go @@ -15,6 +15,7 @@ package main import ( + "bytes" "flag" "fmt" "io" @@ -24,15 +25,11 @@ import ( "sort" "strings" + "android/soong/response" "android/soong/tools/compliance" ) var ( - conditions = newMultiString("c", "License condition to resolve. (may be given multiple times)") - graphViz = flag.Bool("dot", false, "Whether to output graphviz (i.e. dot) format.") - labelConditions = flag.Bool("label_conditions", false, "Whether to label target nodes with conditions.") - stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") - failNoneRequested = fmt.Errorf("\nNo license metadata files requested") failNoLicenses = fmt.Errorf("No licenses found") ) @@ -57,8 +54,44 @@ func (ctx context) strip(installPath string) string { return installPath } -func init() { - flag.Usage = func() { +// newMultiString creates a flag that allows multiple values in an array. +func newMultiString(flags *flag.FlagSet, name, usage string) *multiString { + var f multiString + flags.Var(&f, name, usage) + return &f +} + +// multiString implements the flag `Value` interface for multiple strings. +type multiString []string + +func (ms *multiString) String() string { return strings.Join(*ms, ", ") } +func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } + +func main() { + var expandedArgs []string + for _, arg := range os.Args[1:] { + if strings.HasPrefix(arg, "@") { + f, err := os.Open(strings.TrimPrefix(arg, "@")) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + respArgs, err := response.ReadRspFile(f) + f.Close() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + expandedArgs = append(expandedArgs, respArgs...) + } else { + expandedArgs = append(expandedArgs, arg) + } + } + + flags := flag.NewFlagSet("flags", flag.ExitOnError) + + flags.Usage = func() { fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} Outputs a space-separated Target ActsOn Origin Condition tuple for each @@ -75,32 +108,52 @@ i.e. target:condition1:condition2 etc. Options: `, filepath.Base(os.Args[0])) - flag.PrintDefaults() + flags.PrintDefaults() } -} -// newMultiString creates a flag that allows multiple values in an array. -func newMultiString(name, usage string) *multiString { - var f multiString - flag.Var(&f, name, usage) - return &f -} + conditions := newMultiString(flags, "c", "License condition to resolve. (may be given multiple times)") + graphViz := flags.Bool("dot", false, "Whether to output graphviz (i.e. dot) format.") + labelConditions := flags.Bool("label_conditions", false, "Whether to label target nodes with conditions.") + outputFile := flags.String("o", "-", "Where to write the output. (default stdout)") + stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") -// multiString implements the flag `Value` interface for multiple strings. -type multiString []string - -func (ms *multiString) String() string { return strings.Join(*ms, ", ") } -func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } - -func main() { - flag.Parse() + flags.Parse(expandedArgs) // Must specify at least one root target. - if flag.NArg() == 0 { - flag.Usage() + if flags.NArg() == 0 { + flags.Usage() os.Exit(2) } + if len(*outputFile) == 0 { + flags.Usage() + fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") + os.Exit(2) + } else { + dir, err := filepath.Abs(filepath.Dir(*outputFile)) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err) + os.Exit(1) + } + fi, err := os.Stat(dir) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err) + os.Exit(1) + } + if !fi.IsDir() { + fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile) + os.Exit(1) + } + } + + var ofile io.Writer + ofile = os.Stdout + var obuf *bytes.Buffer + if *outputFile != "-" { + obuf = &bytes.Buffer{} + ofile = obuf + } + lcs := make([]compliance.LicenseCondition, 0, len(*conditions)) for _, name := range *conditions { lcs = append(lcs, compliance.RecognizedConditionNames[name]) @@ -111,14 +164,21 @@ func main() { labelConditions: *labelConditions, stripPrefix: *stripPrefix, } - _, err := dumpResolutions(ctx, os.Stdout, os.Stderr, compliance.FS, flag.Args()...) + _, err := dumpResolutions(ctx, ofile, os.Stderr, compliance.FS, flags.Args()...) if err != nil { if err == failNoneRequested { - flag.Usage() + flags.Usage() } fmt.Fprintf(os.Stderr, "%s\n", err.Error()) os.Exit(1) } + if *outputFile != "-" { + err := os.WriteFile(*outputFile, obuf.Bytes(), 0666) + if err != nil { + fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err) + os.Exit(1) + } + } os.Exit(0) } diff --git a/tools/compliance/cmd/htmlnotice/htmlnotice.go b/tools/compliance/cmd/htmlnotice/htmlnotice.go index e98b27222a..1a4961020e 100644 --- a/tools/compliance/cmd/htmlnotice/htmlnotice.go +++ b/tools/compliance/cmd/htmlnotice/htmlnotice.go @@ -26,19 +26,13 @@ import ( "path/filepath" "strings" + "android/soong/response" "android/soong/tools/compliance" "github.com/google/blueprint/deptools" ) var ( - outputFile = flag.String("o", "-", "Where to write the NOTICE text file. (default stdout)") - depsFile = flag.String("d", "", "Where to write the deps file") - includeTOC = flag.Bool("toc", true, "Whether to include a table of contents.") - product = flag.String("product", "", "The name of the product for which the notice is generated.") - stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") - title = flag.String("title", "", "The title of the notice file.") - failNoneRequested = fmt.Errorf("\nNo license metadata files requested") failNoLicenses = fmt.Errorf("No licenses found") ) @@ -70,23 +64,10 @@ func (ctx context) strip(installPath string) string { return installPath } -func init() { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} - -Outputs an html NOTICE.html or gzipped NOTICE.html.gz file if the -o filename -ends with ".gz". - -Options: -`, filepath.Base(os.Args[0])) - flag.PrintDefaults() - } -} - // newMultiString creates a flag that allows multiple values in an array. -func newMultiString(name, usage string) *multiString { +func newMultiString(flags *flag.FlagSet, name, usage string) *multiString { var f multiString - flag.Var(&f, name, usage) + flags.Var(&f, name, usage) return &f } @@ -97,16 +78,57 @@ func (ms *multiString) String() string { return strings.Join(*ms, ", ") } func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } func main() { - flag.Parse() + var expandedArgs []string + for _, arg := range os.Args[1:] { + if strings.HasPrefix(arg, "@") { + f, err := os.Open(strings.TrimPrefix(arg, "@")) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + respArgs, err := response.ReadRspFile(f) + f.Close() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + expandedArgs = append(expandedArgs, respArgs...) + } else { + expandedArgs = append(expandedArgs, arg) + } + } + + flags := flag.NewFlagSet("flags", flag.ExitOnError) + + flags.Usage = func() { + fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} + +Outputs an html NOTICE.html or gzipped NOTICE.html.gz file if the -o filename +ends with ".gz". + +Options: +`, filepath.Base(os.Args[0])) + flags.PrintDefaults() + } + + outputFile := flags.String("o", "-", "Where to write the NOTICE text file. (default stdout)") + depsFile := flags.String("d", "", "Where to write the deps file") + includeTOC := flags.Bool("toc", true, "Whether to include a table of contents.") + product := flags.String("product", "", "The name of the product for which the notice is generated.") + stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") + title := flags.String("title", "", "The title of the notice file.") + + flags.Parse(expandedArgs) // Must specify at least one root target. - if flag.NArg() == 0 { - flag.Usage() + if flags.NArg() == 0 { + flags.Usage() os.Exit(2) } if len(*outputFile) == 0 { - flag.Usage() + flags.Usage() fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") os.Exit(2) } else { @@ -143,10 +165,10 @@ func main() { ctx := &context{ofile, os.Stderr, compliance.FS, *includeTOC, *product, *stripPrefix, *title, &deps} - err := htmlNotice(ctx, flag.Args()...) + err := htmlNotice(ctx, flags.Args()...) if err != nil { if err == failNoneRequested { - flag.Usage() + flags.Usage() } fmt.Fprintf(os.Stderr, "%s\n", err.Error()) os.Exit(1) diff --git a/tools/compliance/cmd/listshare/listshare.go b/tools/compliance/cmd/listshare/listshare.go index 7f4038b6c5..31bd1b22ac 100644 --- a/tools/compliance/cmd/listshare/listshare.go +++ b/tools/compliance/cmd/listshare/listshare.go @@ -15,6 +15,7 @@ package main import ( + "bytes" "flag" "fmt" "io" @@ -24,12 +25,41 @@ import ( "sort" "strings" + "android/soong/response" "android/soong/tools/compliance" ) -func init() { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, `Usage: %s file.meta_lic {file.meta_lic...} +var ( + failNoneRequested = fmt.Errorf("\nNo license metadata files requested") + failNoLicenses = fmt.Errorf("No licenses found") +) + +func main() { + var expandedArgs []string + for _, arg := range os.Args[1:] { + if strings.HasPrefix(arg, "@") { + f, err := os.Open(strings.TrimPrefix(arg, "@")) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + respArgs, err := response.ReadRspFile(f) + f.Close() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + expandedArgs = append(expandedArgs, respArgs...) + } else { + expandedArgs = append(expandedArgs, arg) + } + } + + flags := flag.NewFlagSet("flags", flag.ExitOnError) + + flags.Usage = func() { + fmt.Fprintf(os.Stderr, `Usage: %s {-o outfile} file.meta_lic {file.meta_lic...} Outputs a csv file with 1 project per line in the first field followed by target:condition pairs describing why the project must be shared. @@ -39,30 +69,61 @@ Soong module or Make target, and the license condition is either restricted (e.g. GPL) or reciprocal (e.g. MPL). `, filepath.Base(os.Args[0])) } -} -var ( - failNoneRequested = fmt.Errorf("\nNo license metadata files requested") - failNoLicenses = fmt.Errorf("No licenses found") -) + outputFile := flags.String("o", "-", "Where to write the list of projects to share. (default stdout)") -func main() { - flag.Parse() + flags.Parse(expandedArgs) // Must specify at least one root target. - if flag.NArg() == 0 { - flag.Usage() + if flags.NArg() == 0 { + flags.Usage() os.Exit(2) } - err := listShare(os.Stdout, os.Stderr, compliance.FS, flag.Args()...) + if len(*outputFile) == 0 { + flags.Usage() + fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") + os.Exit(2) + } else { + dir, err := filepath.Abs(filepath.Dir(*outputFile)) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err) + os.Exit(1) + } + fi, err := os.Stat(dir) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err) + os.Exit(1) + } + if !fi.IsDir() { + fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile) + os.Exit(1) + } + } + + var ofile io.Writer + ofile = os.Stdout + var obuf *bytes.Buffer + if *outputFile != "-" { + obuf = &bytes.Buffer{} + ofile = obuf + } + + err := listShare(ofile, os.Stderr, compliance.FS, flags.Args()...) if err != nil { if err == failNoneRequested { - flag.Usage() + flags.Usage() } fmt.Fprintf(os.Stderr, "%s\n", err.Error()) os.Exit(1) } + if *outputFile != "-" { + err := os.WriteFile(*outputFile, obuf.Bytes(), 0666) + if err != nil { + fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err) + os.Exit(1) + } + } os.Exit(0) } @@ -76,7 +137,7 @@ func listShare(stdout, stderr io.Writer, rootFS fs.FS, files ...string) error { // Read the license graph from the license metadata files (*.meta_lic). licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files) if err != nil { - return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err) + return fmt.Errorf("Unable to read license metadata file(s) %q from %q: %v\n", files, os.Getenv("PWD"), err) } if licenseGraph == nil { return failNoLicenses diff --git a/tools/compliance/cmd/rtrace/rtrace.go b/tools/compliance/cmd/rtrace/rtrace.go index 91171c42e7..667cdcea87 100644 --- a/tools/compliance/cmd/rtrace/rtrace.go +++ b/tools/compliance/cmd/rtrace/rtrace.go @@ -15,6 +15,7 @@ package main import ( + "bytes" "flag" "fmt" "io" @@ -24,21 +25,19 @@ import ( "sort" "strings" + "android/soong/response" "android/soong/tools/compliance" ) var ( - sources = newMultiString("rtrace", "Projects or metadata files to trace back from. (required; multiple allowed)") - stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") - failNoneRequested = fmt.Errorf("\nNo license metadata files requested") failNoSources = fmt.Errorf("\nNo projects or metadata files to trace back from") failNoLicenses = fmt.Errorf("No licenses found") ) type context struct { - sources []string - stripPrefix []string + sources []string + stripPrefix []string } func (ctx context) strip(installPath string) string { @@ -54,8 +53,44 @@ func (ctx context) strip(installPath string) string { return installPath } -func init() { - flag.Usage = func() { +// newMultiString creates a flag that allows multiple values in an array. +func newMultiString(flags *flag.FlagSet, name, usage string) *multiString { + var f multiString + flags.Var(&f, name, usage) + return &f +} + +// multiString implements the flag `Value` interface for multiple strings. +type multiString []string + +func (ms *multiString) String() string { return strings.Join(*ms, ", ") } +func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } + +func main() { + var expandedArgs []string + for _, arg := range os.Args[1:] { + if strings.HasPrefix(arg, "@") { + f, err := os.Open(strings.TrimPrefix(arg, "@")) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + respArgs, err := response.ReadRspFile(f) + f.Close() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + expandedArgs = append(expandedArgs, respArgs...) + } else { + expandedArgs = append(expandedArgs, arg) + } + } + + flags := flag.NewFlagSet("flags", flag.ExitOnError) + + flags.Usage = func() { fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} Outputs a space-separated Target ActsOn Origin Condition tuple for each @@ -72,50 +107,75 @@ i.e. target:condition1:condition2 etc. Options: `, filepath.Base(os.Args[0])) - flag.PrintDefaults() + flags.PrintDefaults() } -} -// newMultiString creates a flag that allows multiple values in an array. -func newMultiString(name, usage string) *multiString { - var f multiString - flag.Var(&f, name, usage) - return &f -} + outputFile := flags.String("o", "-", "Where to write the output. (default stdout)") + sources := newMultiString(flags, "rtrace", "Projects or metadata files to trace back from. (required; multiple allowed)") + stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") -// multiString implements the flag `Value` interface for multiple strings. -type multiString []string - -func (ms *multiString) String() string { return strings.Join(*ms, ", ") } -func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } - -func main() { - flag.Parse() + flags.Parse(expandedArgs) // Must specify at least one root target. - if flag.NArg() == 0 { - flag.Usage() + if flags.NArg() == 0 { + flags.Usage() os.Exit(2) } if len(*sources) == 0 { - flag.Usage() + flags.Usage() fmt.Fprintf(os.Stderr, "\nMust specify at least 1 --rtrace source.\n") os.Exit(2) } - ctx := &context{ - sources: *sources, - stripPrefix: *stripPrefix, + if len(*outputFile) == 0 { + flags.Usage() + fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") + os.Exit(2) + } else { + dir, err := filepath.Abs(filepath.Dir(*outputFile)) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err) + os.Exit(1) + } + fi, err := os.Stat(dir) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err) + os.Exit(1) + } + if !fi.IsDir() { + fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile) + os.Exit(1) + } } - _, err := traceRestricted(ctx, os.Stdout, os.Stderr, compliance.FS, flag.Args()...) + + var ofile io.Writer + ofile = os.Stdout + var obuf *bytes.Buffer + if *outputFile != "-" { + obuf = &bytes.Buffer{} + ofile = obuf + } + + ctx := &context{ + sources: *sources, + stripPrefix: *stripPrefix, + } + _, err := traceRestricted(ctx, ofile, os.Stderr, compliance.FS, flags.Args()...) if err != nil { if err == failNoneRequested { - flag.Usage() + flags.Usage() } fmt.Fprintf(os.Stderr, "%s\n", err.Error()) os.Exit(1) } + if *outputFile != "-" { + err := os.WriteFile(*outputFile, obuf.Bytes(), 0666) + if err != nil { + fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err) + os.Exit(1) + } + } os.Exit(0) } diff --git a/tools/compliance/cmd/shippedlibs/shippedlibs.go b/tools/compliance/cmd/shippedlibs/shippedlibs.go index 9d25dd3de9..add6dd63e8 100644 --- a/tools/compliance/cmd/shippedlibs/shippedlibs.go +++ b/tools/compliance/cmd/shippedlibs/shippedlibs.go @@ -39,9 +39,6 @@ type context struct { rootFS fs.FS } -func init() { -} - func main() { var expandedArgs []string for _, arg := range os.Args[1:] { diff --git a/tools/compliance/cmd/textnotice/textnotice.go b/tools/compliance/cmd/textnotice/textnotice.go index cfa0859a2a..9beaf58ad1 100644 --- a/tools/compliance/cmd/textnotice/textnotice.go +++ b/tools/compliance/cmd/textnotice/textnotice.go @@ -25,18 +25,13 @@ import ( "path/filepath" "strings" + "android/soong/response" "android/soong/tools/compliance" "github.com/google/blueprint/deptools" ) var ( - outputFile = flag.String("o", "-", "Where to write the NOTICE text file. (default stdout)") - depsFile = flag.String("d", "", "Where to write the deps file") - product = flag.String("product", "", "The name of the product for which the notice is generated.") - stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") - title = flag.String("title", "", "The title of the notice file.") - failNoneRequested = fmt.Errorf("\nNo license metadata files requested") failNoLicenses = fmt.Errorf("No licenses found") ) @@ -67,22 +62,10 @@ func (ctx context) strip(installPath string) string { return installPath } -func init() { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} - -Outputs a text NOTICE file. - -Options: -`, filepath.Base(os.Args[0])) - flag.PrintDefaults() - } -} - // newMultiString creates a flag that allows multiple values in an array. -func newMultiString(name, usage string) *multiString { +func newMultiString(flags *flag.FlagSet, name, usage string) *multiString { var f multiString - flag.Var(&f, name, usage) + flags.Var(&f, name, usage) return &f } @@ -93,16 +76,55 @@ func (ms *multiString) String() string { return strings.Join(*ms, ", ") } func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } func main() { - flag.Parse() + var expandedArgs []string + for _, arg := range os.Args[1:] { + if strings.HasPrefix(arg, "@") { + f, err := os.Open(strings.TrimPrefix(arg, "@")) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + respArgs, err := response.ReadRspFile(f) + f.Close() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + expandedArgs = append(expandedArgs, respArgs...) + } else { + expandedArgs = append(expandedArgs, arg) + } + } + + flags := flag.NewFlagSet("flags", flag.ExitOnError) + + flags.Usage = func() { + fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} + +Outputs a text NOTICE file. + +Options: +`, filepath.Base(os.Args[0])) + flags.PrintDefaults() + } + + outputFile := flags.String("o", "-", "Where to write the NOTICE text file. (default stdout)") + depsFile := flags.String("d", "", "Where to write the deps file") + product := flags.String("product", "", "The name of the product for which the notice is generated.") + stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") + title := flags.String("title", "", "The title of the notice file.") + + flags.Parse(expandedArgs) // Must specify at least one root target. - if flag.NArg() == 0 { - flag.Usage() + if flags.NArg() == 0 { + flags.Usage() os.Exit(2) } if len(*outputFile) == 0 { - flag.Usage() + flags.Usage() fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") os.Exit(2) } else { @@ -139,10 +161,10 @@ func main() { ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, *title, &deps} - err := textNotice(ctx, flag.Args()...) + err := textNotice(ctx, flags.Args()...) if err != nil { if err == failNoneRequested { - flag.Usage() + flags.Usage() } fmt.Fprintf(os.Stderr, "%s\n", err.Error()) os.Exit(1) diff --git a/tools/compliance/cmd/xmlnotice/xmlnotice.go b/tools/compliance/cmd/xmlnotice/xmlnotice.go index 84859d738a..2097b7c5e1 100644 --- a/tools/compliance/cmd/xmlnotice/xmlnotice.go +++ b/tools/compliance/cmd/xmlnotice/xmlnotice.go @@ -26,18 +26,13 @@ import ( "path/filepath" "strings" + "android/soong/response" "android/soong/tools/compliance" "github.com/google/blueprint/deptools" ) var ( - outputFile = flag.String("o", "-", "Where to write the NOTICE xml or xml.gz file. (default stdout)") - depsFile = flag.String("d", "", "Where to write the deps file") - product = flag.String("product", "", "The name of the product for which the notice is generated.") - stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") - title = flag.String("title", "", "The title of the notice file.") - failNoneRequested = fmt.Errorf("\nNo license metadata files requested") failNoLicenses = fmt.Errorf("No licenses found") ) @@ -68,23 +63,10 @@ func (ctx context) strip(installPath string) string { return installPath } -func init() { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} - -Outputs an xml NOTICE.xml or gzipped NOTICE.xml.gz file if the -o filename ends -with ".gz". - -Options: -`, filepath.Base(os.Args[0])) - flag.PrintDefaults() - } -} - // newMultiString creates a flag that allows multiple values in an array. -func newMultiString(name, usage string) *multiString { +func newMultiString(flags *flag.FlagSet, name, usage string) *multiString { var f multiString - flag.Var(&f, name, usage) + flags.Var(&f, name, usage) return &f } @@ -95,16 +77,56 @@ func (ms *multiString) String() string { return strings.Join(*ms, ", ") } func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil } func main() { - flag.Parse() + var expandedArgs []string + for _, arg := range os.Args[1:] { + if strings.HasPrefix(arg, "@") { + f, err := os.Open(strings.TrimPrefix(arg, "@")) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + respArgs, err := response.ReadRspFile(f) + f.Close() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + expandedArgs = append(expandedArgs, respArgs...) + } else { + expandedArgs = append(expandedArgs, arg) + } + } + + flags := flag.NewFlagSet("flags", flag.ExitOnError) + + flags.Usage = func() { + fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...} + +Outputs an xml NOTICE.xml or gzipped NOTICE.xml.gz file if the -o filename ends +with ".gz". + +Options: +`, filepath.Base(os.Args[0])) + flags.PrintDefaults() + } + + outputFile := flags.String("o", "-", "Where to write the NOTICE xml or xml.gz file. (default stdout)") + depsFile := flags.String("d", "", "Where to write the deps file") + product := flags.String("product", "", "The name of the product for which the notice is generated.") + stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)") + title := flags.String("title", "", "The title of the notice file.") + + flags.Parse(expandedArgs) // Must specify at least one root target. - if flag.NArg() == 0 { - flag.Usage() + if flags.NArg() == 0 { + flags.Usage() os.Exit(2) } if len(*outputFile) == 0 { - flag.Usage() + flags.Usage() fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n") os.Exit(2) } else { @@ -141,10 +163,10 @@ func main() { ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, *title, &deps} - err := xmlNotice(ctx, flag.Args()...) + err := xmlNotice(ctx, flags.Args()...) if err != nil { if err == failNoneRequested { - flag.Usage() + flags.Usage() } fmt.Fprintf(os.Stderr, "%s\n", err.Error()) os.Exit(1)