This is an automated email from the ASF dual-hosted git repository.

kezhenxu94 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-eyes.git


The following commit(s) were added to refs/heads/main by this push:
     new 78aa037  Provide `--summary` flag to generate the license summary file 
(#103)
78aa037 is described below

commit 78aa037689d813591dd6cc2e9e1b6cf562c6197a
Author: mrproliu <[email protected]>
AuthorDate: Sat May 7 13:48:09 2022 +0800

    Provide `--summary` flag to generate the license summary file (#103)
---
 README.md                 | 310 +++++++++++++++++++++++++++++++++++++++++++++-
 commands/deps_resolve.go  |  55 ++++++--
 pkg/deps/config.go        |   9 +-
 pkg/deps/golang.go        |  31 +++--
 pkg/deps/jar.go           |  20 +--
 pkg/deps/jar_test.go      |   2 +-
 pkg/deps/maven.go         |  22 ++--
 pkg/deps/maven_test.go    |   2 +-
 pkg/deps/npm.go           |  27 ++--
 pkg/deps/npm_test.go      |   2 +-
 pkg/deps/resolve.go       |   4 +-
 pkg/deps/summary.go       | 106 ++++++++++++++++
 pkg/license/identifier.go |  13 +-
 13 files changed, 553 insertions(+), 50 deletions(-)

diff --git a/README.md b/README.md
index f2bb97c..44da58d 100644
--- a/README.md
+++ b/README.md
@@ -157,11 +157,15 @@ INFO Totally checked 20 files, valid: 10, invalid: 10, 
ignored: 0, fixed: 10
 
 This command serves as assistance for human beings to audit the dependencies 
license, it's exit code is always 0.
 
-You can also use the `--output` or `-o` to save the dependencies' `LICENSE` 
files to a specified directory so that
-you can put them in distribution package if needed.
+We also support two flags:
+
+|Flag name|Short name|Description|
+|---------|----------|-----------|
+|`--output`|`-o`|Save the dependencies' `LICENSE` files to a specified 
directory so that you can put them in distribution package if needed.|
+|`--summary`|`-s`|Based on the template, aggregate all dependency information 
and generate a `LICENSE` file.|
 
 ```bash
-license-eye -c test/testdata/.licenserc_for_test_check.yaml dep resolve -o 
./dependencies/licenses
+license-eye -c test/testdata/.licenserc_for_test_check.yaml dep resolve -o 
./dependencies/licenses -s LICENSE.tpl
 ```
 
 <details>
@@ -359,6 +363,298 @@ gopkg.in/check.v1
 
 </details>
 
+##### Summary Template
+
+The summary is a template to generate the summary of dependencies' licenses 
based on the [Golang Template](https://pkg.go.dev/text/template). It includes 
these variables:
+
+|Name|Type|Example|Description|
+|----|----|-------|-----------|
+|LicenseContent|string|`{{.LicenseContent}}`|The project license content, it's 
the license of `header.license.spdx-id` (if set), otherwise it's the 
`header.license.content`. |
+|Groups|list structure|`{{ range .Groups }}`|The dependency groups, all 
licenses are grouped by the same license [SPDX ID](https://spdx.org/licenses/). 
|
+|Groups.LicenseID|string|`{{.LicenseID}}`|The [SPDX 
ID](https://spdx.org/licenses/) of dependency. |
+|Groups.Deps|list structure|`{{ range .Deps }}`|All dependencies with the same 
[SPDX ID](https://spdx.org/licenses/). |
+|Groups.Deps.Name|string|`{{.Name}}`|The name of the dependency. |
+|Groups.Deps.Version|string|`{{.Version}}`|The version of the dependency. |
+|Groups.Deps.LicenseID|string|`{{.LicenseID}}`|The [SPDX 
ID](https://spdx.org/licenses/) of the dependency license. |
+
+<details>
+<summary>Summary Template Generate</summary>
+
+Summary template content:
+```
+{{.LicenseContent }}
+{{ range .Groups }}
+========================================================================
+{{.LicenseID}} licenses
+========================================================================
+{{range .Deps}}
+    {{.Name}} {{.Version}} {{.LicenseID}}
+{{- end }}
+{{ end }}
+```
+
+Generate LICENSE file content:
+```
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+
+========================================================================
+MIT licenses
+========================================================================
+
+    github.com/BurntSushi/toml v0.3.1 MIT
+    github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf MIT
+    github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e MIT
+    github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da MIT
+    github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 MIT
+    github.com/beorn7/perks v1.0.0 MIT
+    github.com/bgentry/speakeasy v0.1.0 MIT
+    github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c MIT
+    github.com/bmatcuk/doublestar/v2 v2.0.4 MIT
+
+========================================================================
+ISC licenses
+========================================================================
+
+    github.com/davecgh/go-spew v1.1.1 ISC
+
+========================================================================
+BSD-2-Clause licenses
+========================================================================
+
+    github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 
BSD-2-Clause
+    github.com/gorilla/websocket v1.4.2 BSD-2-Clause
+    github.com/pkg/errors v0.8.1 BSD-2-Clause
+    github.com/russross/blackfriday/v2 v2.0.1 BSD-2-Clause
+
+========================================================================
+MPL-2.0-no-copyleft-exception licenses
+========================================================================
+
+    github.com/hashicorp/consul/api v1.1.0 MPL-2.0-no-copyleft-exception
+    github.com/hashicorp/consul/sdk v0.1.1 MPL-2.0-no-copyleft-exception
+    github.com/hashicorp/go-cleanhttp v0.5.1 MPL-2.0-no-copyleft-exception
+    github.com/hashicorp/go-immutable-radix v1.0.0 
MPL-2.0-no-copyleft-exception
+    github.com/hashicorp/go-multierror v1.0.0 MPL-2.0-no-copyleft-exception
+    github.com/hashicorp/go-rootcerts v1.0.0 MPL-2.0-no-copyleft-exception
+    github.com/hashicorp/go-sockaddr v1.0.0 MPL-2.0-no-copyleft-exception
+    github.com/hashicorp/go-uuid v1.0.1 MPL-2.0-no-copyleft-exception
+    github.com/hashicorp/golang-lru v0.5.1 MPL-2.0-no-copyleft-exception
+    github.com/hashicorp/logutils v1.0.0 MPL-2.0-no-copyleft-exception
+    github.com/hashicorp/memberlist v0.1.3 MPL-2.0-no-copyleft-exception
+
+========================================================================
+MPL-2.0 licenses
+========================================================================
+
+    github.com/hashicorp/errwrap v1.0.0 MPL-2.0
+    github.com/hashicorp/hcl v1.0.0 MPL-2.0
+    github.com/hashicorp/serf v0.8.2 MPL-2.0
+    github.com/mitchellh/cli v1.0.0 MPL-2.0
+    github.com/mitchellh/gox v0.4.0 MPL-2.0
+
+========================================================================
+MIT and Apache licenses
+========================================================================
+
+    gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 MIT and Apache
+
+========================================================================
+Apache-2.0 licenses
+========================================================================
+
+    cloud.google.com/go v0.46.3 Apache-2.0
+    cloud.google.com/go/bigquery v1.0.1 Apache-2.0
+    cloud.google.com/go/datastore v1.0.0 Apache-2.0
+    cloud.google.com/go/firestore v1.1.0 Apache-2.0
+    cloud.google.com/go/pubsub v1.0.1 Apache-2.0
+    cloud.google.com/go/storage v1.0.0 Apache-2.0
+
+========================================================================
+BSD-3-Clause licenses
+========================================================================
+
+    dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 
BSD-3-Clause
+    github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 BSD-3-Clause
+    github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc 
BSD-3-Clause
+    github.com/fsnotify/fsnotify v1.4.7 BSD-3-Clause
+```
+
+</details>
+
 #### Check Dependencies' licenses
 
 This command can be used to perform automatic license compatibility check, 
when there is incompatible licenses found,
@@ -468,6 +764,10 @@ header: # <1>
 dependency: # <15>
   files: # <16>
     - go.mod
+  license: # <17>
+    - name: dependency-name # <18>
+      version: dependency-version # <19>
+      license: Apache-2.0 # <20>
 ```
 
 1. The `header` section is configurations for source codes license header.
@@ -486,6 +786,10 @@ dependency: # <15>
 14. The `comment_style_id` set the license header comment style, it's the `id` 
at the `styles.yaml`.
 15. The `dependency` section is configurations for resolving dependencies' 
licenses.
 16. The `files` are the files that declare the dependencies of a project, 
typically, `go.mod` in Go project, `pom.xml` in maven project, and 
`package.json` in NodeJS project. If it's a relative path, it's relative to the 
`.licenserc.yaml`.
+17. Declare the licenses which cannot be identified by this tool.
+18. The `name` of the dependency, The name is different for different 
projects, `PackagePath` in Go project, `GroupID:ArtifactID` in maven project, 
`PackageName` in NodeJS project.
+19. The `version` of the dependency, it's locked, preventing license changed 
between different versions.
+20. The [SPDX ID](https://spdx.org/licenses/) of the dependency license.
 
 **NOTE**: When the `SPDX-ID` is Apache-2.0 and the owner is Apache Software 
foundation, the content would be [a dedicated 
license](https://www.apache.org/legal/src-headers.html#headers) specified by 
the ASF, otherwise, the license would be [the standard 
one](https://www.apache.org/foundation/license-faq.html#Apply-My-Software).
 
diff --git a/commands/deps_resolve.go b/commands/deps_resolve.go
index d242eeb..4c8e197 100644
--- a/commands/deps_resolve.go
+++ b/commands/deps_resolve.go
@@ -23,6 +23,7 @@ import (
        "path/filepath"
        "regexp"
        "strings"
+       "text/template"
 
        "github.com/spf13/cobra"
 
@@ -31,10 +32,15 @@ import (
 )
 
 var outDir string
+var summaryTplPath string
+var summaryTpl *template.Template
 
 func init() {
        DepsResolveCommand.PersistentFlags().StringVarP(&outDir, "output", "o", 
"",
                "the directory to output the resolved dependencies' licenses, 
if not set the dependencies' licenses won't be saved")
+       DepsResolveCommand.PersistentFlags().StringVarP(&summaryTplPath, 
"summary", "s", "",
+               "the template file to write the summary of dependencies' 
licenses, a new file named \"LICENSE\" will be "+
+                       "created in the same directory as the template file, to 
save the final summary.")
 }
 
 var fileNamePattern = regexp.MustCompile(`[^a-zA-Z0-9\\.\-]`)
@@ -44,16 +50,27 @@ var DepsResolveCommand = &cobra.Command{
        Aliases: []string{"r"},
        Long:    "resolves all dependencies of a module and their transitive 
dependencies",
        PreRunE: func(cmd *cobra.Command, args []string) error {
-               if outDir == "" {
-                       return nil
-               }
-               absPath, err := filepath.Abs(outDir)
-               if err != nil {
-                       return err
+               if outDir != "" {
+                       absPath, err := filepath.Abs(outDir)
+                       if err != nil {
+                               return err
+                       }
+                       outDir = absPath
+                       if err := os.MkdirAll(outDir, 0o700); err != nil && 
!os.IsExist(err) {
+                               return err
+                       }
                }
-               outDir = absPath
-               if err := os.MkdirAll(outDir, 0o700); err != nil && 
!os.IsExist(err) {
-                       return err
+               if summaryTplPath != "" {
+                       absPath, err := filepath.Abs(summaryTplPath)
+                       if err != nil {
+                               return err
+                       }
+                       summaryTplPath = absPath
+                       tpl, err := deps.ParseTemplate(summaryTplPath)
+                       if err != nil {
+                               return err
+                       }
+                       summaryTpl = tpl
                }
                return nil
        },
@@ -64,6 +81,12 @@ var DepsResolveCommand = &cobra.Command{
                        return err
                }
 
+               if summaryTpl != nil {
+                       if err := writeSummary(&report); err != nil {
+                               return err
+                       }
+               }
+
                if outDir != "" {
                        for _, result := range report.Resolved {
                                writeLicense(result)
@@ -102,3 +125,17 @@ func writeLicense(result *deps.Result) {
                return
        }
 }
+
+func writeSummary(rep *deps.Report) error {
+       file, err := os.Create(filepath.Join(filepath.Dir(summaryTplPath), 
"LICENSE"))
+       if err != nil {
+               return err
+       }
+       defer file.Close()
+       summary, err := deps.GenerateSummary(summaryTpl, &Config.Header, rep)
+       if err != nil {
+               return err
+       }
+       _, err = file.WriteString(summary)
+       return err
+}
diff --git a/pkg/deps/config.go b/pkg/deps/config.go
index 0633b96..64c0a53 100644
--- a/pkg/deps/config.go
+++ b/pkg/deps/config.go
@@ -23,7 +23,14 @@ import (
 )
 
 type ConfigDeps struct {
-       Files []string `yaml:"files"`
+       Files   []string            `yaml:"files"`
+       License []*ConfigDepLicense `yaml:"licenses"`
+}
+
+type ConfigDepLicense struct {
+       Name    string `yaml:"name"`
+       Version string `yaml:"version"`
+       License string `yaml:"license"`
 }
 
 func (config *ConfigDeps) Finalize(configFile string) error {
diff --git a/pkg/deps/golang.go b/pkg/deps/golang.go
index 063fbf3..a65c20c 100644
--- a/pkg/deps/golang.go
+++ b/pkg/deps/golang.go
@@ -45,7 +45,7 @@ func (resolver *GoModResolver) CanResolve(file string) bool {
 }
 
 // Resolve resolves licenses of all dependencies declared in the go.mod file.
-func (resolver *GoModResolver) Resolve(goModFile string, report *Report) error 
{
+func (resolver *GoModResolver) Resolve(goModFile string, licenses 
[]*ConfigDepLicense, report *Report) error {
        if err := os.Chdir(filepath.Dir(goModFile)); err != nil {
                return err
        }
@@ -78,13 +78,19 @@ func (resolver *GoModResolver) Resolve(goModFile string, 
report *Report) error {
 
        logger.Log.Debugln("Module size:", len(modules))
 
-       return resolver.ResolvePackages(modules, report)
+       return resolver.ResolvePackages(modules, licenses, report)
 }
 
 // ResolvePackages resolves the licenses of the given packages.
-func (resolver *GoModResolver) ResolvePackages(modules []*packages.Module, 
report *Report) error {
+func (resolver *GoModResolver) ResolvePackages(modules []*packages.Module, 
licenses []*ConfigDepLicense, report *Report) error {
        for _, module := range modules {
-               err := resolver.ResolvePackageLicense(module, report)
+               var decalreLicense *ConfigDepLicense
+               for _, l := range licenses {
+                       if l.Name == module.Path && l.Version == module.Version 
{
+                               decalreLicense = l
+                       }
+               }
+               err := resolver.ResolvePackageLicense(module, decalreLicense, 
report)
                if err != nil {
                        logger.Log.Warnf("Failed to resolve the license of 
<%s>: %v\n", module.Path, err)
                        report.Skip(&Result{
@@ -99,7 +105,7 @@ func (resolver *GoModResolver) ResolvePackages(modules 
[]*packages.Module, repor
 
 var possibleLicenseFileName = 
regexp.MustCompile(`(?i)^LICENSE|LICENCE(\.txt)?|COPYING(\.txt)?$`)
 
-func (resolver *GoModResolver) ResolvePackageLicense(module *packages.Module, 
report *Report) error {
+func (resolver *GoModResolver) ResolvePackageLicense(module *packages.Module, 
declareLicense *ConfigDepLicense, report *Report) error {
        dir := module.Dir
 
        for {
@@ -117,15 +123,22 @@ func (resolver *GoModResolver) 
ResolvePackageLicense(module *packages.Module, re
                        if err != nil {
                                return err
                        }
-                       identifier, err := license.Identify(module.Path, 
string(content))
-                       if err != nil {
-                               return err
+                       var licenseID string
+                       if declareLicense != nil {
+                               licenseID = declareLicense.License
+                       } else {
+                               identifier, err := 
license.Identify(module.Path, string(content))
+                               if err != nil {
+                                       return err
+                               }
+                               licenseID = identifier
                        }
+
                        report.Resolve(&Result{
                                Dependency:      module.Path,
                                LicenseFilePath: licenseFilePath,
                                LicenseContent:  string(content),
-                               LicenseSpdxID:   identifier,
+                               LicenseSpdxID:   licenseID,
                                Version:         module.Version,
                        })
                        return nil
diff --git a/pkg/deps/jar.go b/pkg/deps/jar.go
index fada99c..0121da7 100644
--- a/pkg/deps/jar.go
+++ b/pkg/deps/jar.go
@@ -37,7 +37,7 @@ func (resolver *JarResolver) CanResolve(jarFile string) bool {
        return filepath.Ext(jarFile) == ".jar"
 }
 
-func (resolver *JarResolver) Resolve(jarFile string, report *Report) error {
+func (resolver *JarResolver) Resolve(jarFile string, licenses 
[]*ConfigDepLicense, report *Report) error {
        state := NotFound
        if err := resolver.ResolveJar(&state, jarFile, Unknown, report); err != 
nil {
                dep := filepath.Base(jarFile)
@@ -76,7 +76,7 @@ func (resolver *JarResolver) ResolveJar(state *State, 
jarFile, version string, r
                                return err
                        }
 
-                       return resolver.IdentifyLicense(jarFile, dep, 
buf.String(), version, report)
+                       return resolver.IdentifyLicense(jarFile, dep, 
buf.String(), version, nil, report)
                }
        }
 
@@ -122,17 +122,23 @@ func (resolver *JarResolver) ReadFileFromZip(archiveFile 
*zip.File) (*bytes.Buff
        return buf, nil
 }
 
-func (resolver *JarResolver) IdentifyLicense(path, dep, content, version 
string, report *Report) error {
-       identifier, err := license.Identify(path, content)
-       if err != nil {
-               return err
+func (resolver *JarResolver) IdentifyLicense(path, dep, content, version 
string, declareLicense *ConfigDepLicense, report *Report) error {
+       var licenseID string
+       if declareLicense != nil {
+               licenseID = declareLicense.License
+       } else {
+               identifier, err := license.Identify(path, content)
+               if err != nil {
+                       return err
+               }
+               licenseID = identifier
        }
 
        report.Resolve(&Result{
                Dependency:      dep,
                LicenseFilePath: path,
                LicenseContent:  content,
-               LicenseSpdxID:   identifier,
+               LicenseSpdxID:   licenseID,
                Version:         version,
        })
        return nil
diff --git a/pkg/deps/jar_test.go b/pkg/deps/jar_test.go
index de31b0b..6215685 100644
--- a/pkg/deps/jar_test.go
+++ b/pkg/deps/jar_test.go
@@ -132,7 +132,7 @@ func TestResolveJar(t *testing.T) {
                report := deps.Report{}
                for _, jar := range jars {
                        if resolver.CanResolve(jar) {
-                               if err := resolver.Resolve(jar, &report); err 
!= nil {
+                               if err := resolver.Resolve(jar, nil, &report); 
err != nil {
                                        t.Error(err)
                                        return
                                }
diff --git a/pkg/deps/maven.go b/pkg/deps/maven.go
index 3274074..28d1ce3 100644
--- a/pkg/deps/maven.go
+++ b/pkg/deps/maven.go
@@ -48,7 +48,7 @@ func (resolver *MavenPomResolver) CanResolve(mavenPomFile 
string) bool {
 }
 
 // Resolve resolves licenses of all dependencies declared in the pom.xml file.
-func (resolver *MavenPomResolver) Resolve(mavenPomFile string, report *Report) 
error {
+func (resolver *MavenPomResolver) Resolve(mavenPomFile string, licenses 
[]*ConfigDepLicense, report *Report) error {
        if err := os.Chdir(filepath.Dir(mavenPomFile)); err != nil {
                return err
        }
@@ -70,7 +70,7 @@ func (resolver *MavenPomResolver) Resolve(mavenPomFile 
string, report *Report) e
                }
        }
 
-       return resolver.ResolveDependencies(deps, report)
+       return resolver.ResolveDependencies(deps, licenses, report)
 }
 
 // CheckMVN check available maven tools, find local repositories and download 
all dependencies
@@ -142,10 +142,16 @@ func (resolver *MavenPomResolver) LoadDependencies() 
([]*Dependency, error) {
 }
 
 // ResolveDependencies resolves the licenses of the given dependencies
-func (resolver *MavenPomResolver) ResolveDependencies(deps []*Dependency, 
report *Report) error {
+func (resolver *MavenPomResolver) ResolveDependencies(deps []*Dependency, 
licenses []*ConfigDepLicense, report *Report) error {
        for _, dep := range deps {
                state := NotFound
-               err := resolver.ResolveLicense(&state, dep, report)
+               var declareLicense *ConfigDepLicense
+               for _, l := range licenses {
+                       if l.Name == fmt.Sprintf("%s:%s", dep.GroupID, 
dep.ArtifactID) && l.Version == dep.Version {
+                               declareLicense = l
+                       }
+               }
+               err := resolver.ResolveLicense(&state, dep, declareLicense, 
report)
                if err != nil {
                        logger.Log.Warnf("Failed to resolve the license of 
<%s>: %v\n", dep.Jar(), state.String())
                        report.Skip(&Result{
@@ -159,17 +165,17 @@ func (resolver *MavenPomResolver) 
ResolveDependencies(deps []*Dependency, report
 }
 
 // ResolveLicense search all possible locations of the license, such as pom 
file, jar package
-func (resolver *MavenPomResolver) ResolveLicense(state *State, dep 
*Dependency, report *Report) error {
+func (resolver *MavenPomResolver) ResolveLicense(state *State, dep 
*Dependency, declareLicense *ConfigDepLicense, report *Report) error {
        err := resolver.ResolveJar(state, filepath.Join(resolver.repo, 
dep.Path(), dep.Jar()), dep.Version, report)
        if err == nil {
                return nil
        }
 
-       return resolver.ResolveLicenseFromPom(state, dep, report)
+       return resolver.ResolveLicenseFromPom(state, dep, declareLicense, 
report)
 }
 
 // ResolveLicenseFromPom search for license in the pom file, which may appear 
in the header comments or in license element of xml
-func (resolver *MavenPomResolver) ResolveLicenseFromPom(state *State, dep 
*Dependency, report *Report) (err error) {
+func (resolver *MavenPomResolver) ResolveLicenseFromPom(state *State, dep 
*Dependency, declareLicense *ConfigDepLicense, report *Report) (err error) {
        pomFile := filepath.Join(resolver.repo, dep.Path(), dep.Pom())
 
        pom, err := resolver.ReadLicensesFromPom(pomFile)
@@ -192,7 +198,7 @@ func (resolver *MavenPomResolver) 
ResolveLicenseFromPom(state *State, dep *Depen
                return err
        } else if headerComments != "" {
                *state |= FoundLicenseInPomHeader
-               return resolver.IdentifyLicense(pomFile, dep.Jar(), 
headerComments, dep.Version, report)
+               return resolver.IdentifyLicense(pomFile, dep.Jar(), 
headerComments, dep.Version, declareLicense, report)
        }
 
        return fmt.Errorf("not found in pom file")
diff --git a/pkg/deps/maven_test.go b/pkg/deps/maven_test.go
index 5c4b797..75c6ae0 100644
--- a/pkg/deps/maven_test.go
+++ b/pkg/deps/maven_test.go
@@ -113,7 +113,7 @@ func TestResolveMaven(t *testing.T) {
 
                if resolver.CanResolve(pomFile) {
                        report := deps.Report{}
-                       if err := resolver.Resolve(pomFile, &report); err != 
nil {
+                       if err := resolver.Resolve(pomFile, nil, &report); err 
!= nil {
                                t.Error(err)
                                return
                        }
diff --git a/pkg/deps/npm.go b/pkg/deps/npm.go
index 77e814e..cbecd4e 100644
--- a/pkg/deps/npm.go
+++ b/pkg/deps/npm.go
@@ -63,7 +63,7 @@ func (resolver *NpmResolver) CanResolve(file string) bool {
 }
 
 // Resolve resolves licenses of all dependencies declared in the package.json 
file.
-func (resolver *NpmResolver) Resolve(pkgFile string, report *Report) error {
+func (resolver *NpmResolver) Resolve(pkgFile string, licenses 
[]*ConfigDepLicense, report *Report) error {
        workDir := filepath.Dir(pkgFile)
        if err := os.Chdir(workDir); err != nil {
                return err
@@ -85,7 +85,7 @@ func (resolver *NpmResolver) Resolve(pkgFile string, report 
*Report) error {
        // Walk through each package's root directory to resolve licenses
        // Resolve from a package's package.json file or its license file
        for _, pkg := range pkgs {
-               if result := resolver.ResolvePackageLicense(pkg.Name, 
pkg.Path); result.LicenseSpdxID != "" {
+               if result := resolver.ResolvePackageLicense(pkg.Name, pkg.Path, 
licenses); result.LicenseSpdxID != "" {
                        report.Resolve(result)
                } else {
                        result.LicenseSpdxID = Unknown
@@ -185,17 +185,17 @@ func (resolver *NpmResolver) GetInstalledPkgs(pkgDir 
string) []*Package {
 // First, try to find and parse the package's package.json file to check the 
license file
 // If the previous step fails, then try to identify the package's LICENSE file
 // It's a necessary procedure to check the LICENSE file, because the resolver 
needs to record the license content
-func (resolver *NpmResolver) ResolvePackageLicense(pkgName, pkgPath string) 
*Result {
+func (resolver *NpmResolver) ResolvePackageLicense(pkgName, pkgPath string, 
licenses []*ConfigDepLicense) *Result {
        result := &Result{
                Dependency: pkgName,
        }
        // resolve from the package.json file
-       if err := resolver.ResolvePkgFile(result, pkgPath); err != nil {
+       if err := resolver.ResolvePkgFile(result, pkgPath, licenses); err != 
nil {
                result.ResolveErrors = append(result.ResolveErrors, err)
        }
 
        // resolve from the LICENSE file
-       if err := resolver.ResolveLcsFile(result, pkgPath); err != nil {
+       if err := resolver.ResolveLcsFile(result, pkgPath, licenses); err != 
nil {
                result.ResolveErrors = append(result.ResolveErrors, err)
        }
 
@@ -203,7 +203,7 @@ func (resolver *NpmResolver) ResolvePackageLicense(pkgName, 
pkgPath string) *Res
 }
 
 // ResolvePkgFile tries to find and parse the package.json file to capture the 
license field
-func (resolver *NpmResolver) ResolvePkgFile(result *Result, pkgPath string) 
error {
+func (resolver *NpmResolver) ResolvePkgFile(result *Result, pkgPath string, 
licenses []*ConfigDepLicense) error {
        expectedPkgFile := filepath.Join(pkgPath, PkgFileName)
        packageInfo, err := resolver.ParsePkgFile(expectedPkgFile)
        if err != nil {
@@ -211,6 +211,13 @@ func (resolver *NpmResolver) ResolvePkgFile(result 
*Result, pkgPath string) erro
        }
 
        result.Version = packageInfo.Version
+       for _, l := range licenses {
+               if l.Name == packageInfo.Name && l.Version == 
packageInfo.Version {
+                       result.LicenseSpdxID = l.License
+                       return nil
+               }
+       }
+
        if lcs, ok := resolver.ResolveLicenseField(packageInfo.License); ok {
                result.LicenseSpdxID = lcs
                return nil
@@ -259,7 +266,7 @@ func (resolver *NpmResolver) ResolveLicensesField(licenses 
[]Lcs) (string, bool)
 }
 
 // ResolveLcsFile tries to find the license file to identify the license
-func (resolver *NpmResolver) ResolveLcsFile(result *Result, pkgPath string) 
error {
+func (resolver *NpmResolver) ResolveLcsFile(result *Result, pkgPath string, 
licenses []*ConfigDepLicense) error {
        depFiles, err := os.ReadDir(pkgPath)
        if err != nil {
                return err
@@ -278,6 +285,12 @@ func (resolver *NpmResolver) ResolveLcsFile(result 
*Result, pkgPath string) erro
                if result.LicenseSpdxID != "" {
                        return nil
                }
+               for _, l := range licenses {
+                       if l.Name == info.Name() && l.Version == result.Version 
{
+                               result.LicenseSpdxID = l.License
+                               return nil
+                       }
+               }
                identifier, err := license.Identify(result.Dependency, 
string(content))
                if err != nil {
                        return err
diff --git a/pkg/deps/npm_test.go b/pkg/deps/npm_test.go
index f30cba5..5d76110 100644
--- a/pkg/deps/npm_test.go
+++ b/pkg/deps/npm_test.go
@@ -95,7 +95,7 @@ func TestResolvePkgFile(t *testing.T) {
                if err != nil {
                        t.Fatal(err)
                }
-               err = resolver.ResolvePkgFile(result, f.Name())
+               err = resolver.ResolvePkgFile(result, f.Name(), nil)
                if result.LicenseSpdxID != data.result && (err != nil) == 
data.hasErr {
                        t.Fail()
                }
diff --git a/pkg/deps/resolve.go b/pkg/deps/resolve.go
index 3c0d3b4..325b9d0 100644
--- a/pkg/deps/resolve.go
+++ b/pkg/deps/resolve.go
@@ -23,7 +23,7 @@ import (
 
 type Resolver interface {
        CanResolve(string) bool
-       Resolve(string, *Report) error
+       Resolve(string, []*ConfigDepLicense, *Report) error
 }
 
 var Resolvers = []Resolver{
@@ -39,7 +39,7 @@ resolveFile:
                        if !resolver.CanResolve(file) {
                                continue
                        }
-                       if err := resolver.Resolve(file, report); err != nil {
+                       if err := resolver.Resolve(file, config.License, 
report); err != nil {
                                return err
                        }
                        continue resolveFile
diff --git a/pkg/deps/summary.go b/pkg/deps/summary.go
new file mode 100644
index 0000000..d14acb7
--- /dev/null
+++ b/pkg/deps/summary.go
@@ -0,0 +1,106 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 deps
+
+import (
+       "bytes"
+       "os"
+       "text/template"
+
+       "github.com/apache/skywalking-eyes/pkg/header"
+       "github.com/apache/skywalking-eyes/pkg/license"
+)
+
+type SummaryRenderContext struct {
+       LicenseContent string                       // Current project license 
content
+       Groups         []*SummaryRenderLicenseGroup // All dependency license 
groups
+}
+
+type SummaryRenderLicenseGroup struct {
+       LicenseID string                  // Aggregate all same license ID 
dependencies
+       Deps      []*SummaryRenderLicense // Same license ID dependencies
+}
+
+type SummaryRenderLicense struct {
+       Name      string // Dependency name
+       Version   string // Dependency version
+       LicenseID string // License ID
+}
+
+func ParseTemplate(path string) (*template.Template, error) {
+       tpl, err := os.ReadFile(path)
+       if err != nil {
+               return nil, err
+       }
+       return template.New("summary").Parse(string(tpl))
+}
+
+// GenerateSummary generate the summary content by template, license config 
and dependency report
+func GenerateSummary(tpl *template.Template, head *header.ConfigHeader, rep 
*Report) (string, error) {
+       var r bytes.Buffer
+       context, err := generateSummaryRenderContext(head, rep)
+       if err != nil {
+               return "", err
+       }
+       if err := tpl.Execute(&r, context); err != nil {
+               return "", err
+       }
+       return r.String(), nil
+}
+
+func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) 
(*SummaryRenderContext, error) {
+       // the license id of the project
+       var headerContent string
+       if head.License.SpdxID != "" {
+               c, err := license.GetLicenseContent(head.License.SpdxID)
+               if err != nil {
+                       return nil, err
+               }
+               headerContent = c
+       }
+       if headerContent == "" {
+               headerContent = head.GetLicenseContent()
+       }
+
+       groups := make(map[string]*SummaryRenderLicenseGroup)
+       for _, r := range rep.Resolved {
+               group := groups[r.LicenseSpdxID]
+               if group == nil {
+                       group = &SummaryRenderLicenseGroup{
+                               LicenseID: r.LicenseSpdxID,
+                               Deps:      make([]*SummaryRenderLicense, 0),
+                       }
+                       groups[r.LicenseSpdxID] = group
+               }
+
+               group.Deps = append(group.Deps, &SummaryRenderLicense{
+                       Name:      r.Dependency,
+                       Version:   r.Version,
+                       LicenseID: r.LicenseSpdxID,
+               })
+       }
+
+       groupArray := make([]*SummaryRenderLicenseGroup, 0)
+       for _, g := range groups {
+               groupArray = append(groupArray, g)
+       }
+       return &SummaryRenderContext{
+               LicenseContent: headerContent,
+               Groups:         groupArray,
+       }, nil
+}
diff --git a/pkg/license/identifier.go b/pkg/license/identifier.go
index 7a09a15..c23ea75 100644
--- a/pkg/license/identifier.go
+++ b/pkg/license/identifier.go
@@ -29,8 +29,10 @@ import (
        "github.com/apache/skywalking-eyes/internal/logger"
 )
 
+var licenseTemplatesDir = "lcs-templates"
+
 var templatesDirs = []string{
-       "lcs-templates",
+       licenseTemplatesDir,
        // Some projects simply use the header text as their LICENSE content...
        "header-templates",
 }
@@ -102,3 +104,12 @@ func Identify(pkgPath, content string) (string, error) {
                return "", fmt.Errorf("cannot identify license content")
        }
 }
+
+// GetLicenseContent from license id
+func GetLicenseContent(spdxID string) (string, error) {
+       res, err := assets.Asset(filepath.Join(licenseTemplatesDir, 
spdxID+".txt"))
+       if err != nil {
+               return "", err
+       }
+       return string(res), nil
+}

Reply via email to