Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide --summary flag to generate the license summary file #103

Merged
merged 7 commits into from
May 7, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,12 @@ 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.

We also support two flag:
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.|
|`--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 -s LICENSE.tpl
kezhenxu94 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -494,9 +494,9 @@ 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 license which cannot be identified as a dependency.
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, prevent version update and change the License.
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).
Expand Down
32 changes: 18 additions & 14 deletions commands/deps_resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"text/template"

Expand All @@ -38,9 +39,11 @@ 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 the dependencies' licenses, if not set the licenses just print as the table")
"the template file to write the summary the dependencies' licenses into \"LICENSE\" file into directory which same with template file.")
mrproliu marked this conversation as resolved.
Show resolved Hide resolved
}

var fileNamePattern = regexp.MustCompile(`[^a-zA-Z0-9\\.\-]`)

var DepsResolveCommand = &cobra.Command{
Use: "resolve",
Aliases: []string{"r"},
Expand All @@ -57,9 +60,11 @@ var DepsResolveCommand = &cobra.Command{
}
}
if summaryTplPath != "" {
if outDir == "" {
return fmt.Errorf("please provide the output directory to write the license summary file")
absPath, err := filepath.Abs(summaryTplPath)
if err != nil {
return err
}
summaryTplPath = absPath
tpl, err := deps.ParseTemplate(summaryTplPath)
if err != nil {
return err
Expand All @@ -82,7 +87,9 @@ var DepsResolveCommand = &cobra.Command{
}

if summaryTpl != nil {
writeSummary(&report)
if err := writeSummary(&report); err != nil {
logger.Log.Warnf("write summary file error: %v", err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about

Suggested change
logger.Log.Warnf("write summary file error: %v", err)
return err

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to perform before writing licenses if we need to throw the exception, it could make the command more consistent.

}
}

fmt.Println(report.String())
Expand All @@ -103,7 +110,8 @@ var DepsResolveCommand = &cobra.Command{
}

func writeLicense(result *deps.Result) {
filename := filepath.Join(outDir, deps.GenerateDependencyLicenseFilename(result))
filename := string(fileNamePattern.ReplaceAll([]byte(result.Dependency), []byte("-")))
filename = filepath.Join(outDir, "license-"+filename+".txt")
file, err := os.Create(filename)
if err != nil {
logger.Log.Errorf("failed to create license file %v: %v", filename, err)
Expand All @@ -117,20 +125,16 @@ func writeLicense(result *deps.Result) {
}
}

func writeSummary(rep *deps.Report) {
file, err := os.Create(filepath.Join(outDir, "LICENSE"))
func writeSummary(rep *deps.Report) error {
file, err := os.Create(filepath.Join(filepath.Dir(summaryTplPath), "LICENSE"))
if err != nil {
logger.Log.Errorf("failed to create summary license file %s: %v", "LICENSE", err)
return
return err
}
defer file.Close()
summary, err := deps.GenerateSummary(summaryTpl, &Config.Header, rep)
if err != nil {
logger.Log.Errorf("failed to generate summary content: %v", err)
return
return err
}
_, err = file.WriteString(summary)
if err != nil {
logger.Log.Errorf("failed to write summary file, %v", err)
}
return err
}
14 changes: 9 additions & 5 deletions pkg/deps/golang.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,18 +123,22 @@ func (resolver *GoModResolver) ResolvePackageLicense(module *packages.Module, de
if err != nil {
return err
}
identifier, err := license.Identify(module.Path, string(content))
if err != nil {
if declareLicense == nil {
var licenseID string
if declareLicense != nil {
licenseID = declareLicense.License
} else {
identifier, err := license.Identify(module.Path, string(content))
if err != nil {
return err
}
identifier = declareLicense.License
licenseID = identifier
}

report.Resolve(&Result{
Dependency: module.Path,
LicenseFilePath: licenseFilePath,
LicenseContent: string(content),
LicenseSpdxID: identifier,
LicenseSpdxID: licenseID,
Version: module.Version,
})
return nil
Expand Down
13 changes: 8 additions & 5 deletions pkg/deps/jar.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,19 +123,22 @@ func (resolver *JarResolver) ReadFileFromZip(archiveFile *zip.File) (*bytes.Buff
}

func (resolver *JarResolver) IdentifyLicense(path, dep, content, version string, declareLicense *ConfigDepLicense, report *Report) error {
identifier, err := license.Identify(path, content)
if err != nil {
if declareLicense == nil {
var licenseID string
if declareLicense != nil {
licenseID = declareLicense.License
} else {
identifier, err := license.Identify(path, content)
if err != nil {
return err
}
identifier = declareLicense.License
licenseID = identifier
}

report.Resolve(&Result{
Dependency: dep,
LicenseFilePath: path,
LicenseContent: content,
LicenseSpdxID: identifier,
LicenseSpdxID: licenseID,
Version: version,
})
return nil
Expand Down
25 changes: 19 additions & 6 deletions pkg/deps/npm.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (resolver *NpmResolver) Resolve(pkgFile string, licenses []*ConfigDepLicens
// 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 {
if resolver.tryToResolve(pkg, licenses, result) {
Expand Down Expand Up @@ -200,32 +200,39 @@ 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)
}

return result
}

// 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 {
return err
}

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
Expand Down Expand Up @@ -274,7 +281,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
Expand All @@ -293,6 +300,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
Expand Down
2 changes: 1 addition & 1 deletion pkg/deps/npm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
37 changes: 11 additions & 26 deletions pkg/deps/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,12 @@ package deps
import (
"bytes"
"os"
"path/filepath"
"regexp"
"text/template"

"github.com/apache/skywalking-eyes/pkg/header"
"github.com/apache/skywalking-eyes/pkg/license"
)

var fileNamePattern = regexp.MustCompile(`[^a-zA-Z0-9\\.\-]`)

type SummaryRenderContext struct {
LicenseContent string // Current project license content
Groups []*SummaryRenderLicenseGroup // All dependency license groups
Expand All @@ -43,21 +39,11 @@ type SummaryRenderLicenseGroup struct {
type SummaryRenderLicense struct {
Name string // Dependency name
Version string // Dependency version
Location string // The filename of generated license file
LicenseID string // License ID
}

func GenerateDependencyLicenseFilename(result *Result) string {
filename := string(fileNamePattern.ReplaceAll([]byte(result.Dependency), []byte("-")))
return "license-" + filename + ".txt"
}

func ParseTemplate(path string) (*template.Template, error) {
absPath, err := filepath.Abs(path)
if err != nil {
return nil, err
}
tpl, err := os.ReadFile(absPath)
tpl, err := os.ReadFile(path)
if err != nil {
return nil, err
}
Expand All @@ -67,21 +53,21 @@ func ParseTemplate(path string) (*template.Template, error) {
// 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
}
context := generateSummaryRenderContext(head, rep)
if err := tpl.Execute(&r, context); err != nil {
return "", err
}
return r.String(), nil
}

func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) (*SummaryRenderContext, error) {
func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) *SummaryRenderContext {
// the license id of the project
licenseContent, err := license.GetLicenseContent(head.License.SpdxID)
if err != nil {
return nil, err
var headerContent string
if head.License.SpdxID != "" {
headerContent, _ = license.GetLicenseContent(head.License.SpdxID)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the error can be ignored, if the users specify an unknown SpdxID (by mistake), we should notify that

Copy link
Contributor Author

@mrproliu mrproliu May 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. If GetLicenseContent could not be found, then throw an error.

}
if headerContent == "" {
headerContent = head.GetLicenseContent()
}

groups := make(map[string]*SummaryRenderLicenseGroup)
Expand All @@ -98,7 +84,6 @@ func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) (*Summ
group.Deps = append(group.Deps, &SummaryRenderLicense{
Name: r.Dependency,
Version: r.Version,
Location: GenerateDependencyLicenseFilename(r),
LicenseID: r.LicenseSpdxID,
})
}
Expand All @@ -108,7 +93,7 @@ func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) (*Summ
groupArray = append(groupArray, g)
}
return &SummaryRenderContext{
LicenseContent: licenseContent,
LicenseContent: headerContent,
Groups: groupArray,
}, nil
}
}