Support license expression in dep check. (#120)
diff --git a/pkg/deps/check.go b/pkg/deps/check.go
index d00a1f2..15bc546 100644
--- a/pkg/deps/check.go
+++ b/pkg/deps/check.go
@@ -28,12 +28,21 @@
"github.com/apache/skywalking-eyes/internal/logger"
)
-type compatibilityMatrix struct {
+type CompatibilityMatrix struct {
Compatible []string `yaml:"compatible"`
Incompatible []string `yaml:"incompatible"`
}
-var matrices = make(map[string]compatibilityMatrix)
+var matrices = make(map[string]CompatibilityMatrix)
+
+type LicenseOperator int
+
+const (
+ LicenseOperatorNone LicenseOperator = iota
+ LicenseOperatorAND
+ LicenseOperatorOR
+ LicenseOperatorWITH
+)
func init() {
dir := "compatibility"
@@ -43,7 +52,7 @@
}
for _, file := range files {
name := file.Name()
- matrix := compatibilityMatrix{}
+ matrix := CompatibilityMatrix{}
if bytes, err := assets.Asset(filepath.Join(dir, name)); err != nil {
logger.Log.Fatalln("Failed to read compatibility file:", name, err)
} else if err := yaml.Unmarshal(bytes, &matrix); err != nil {
@@ -54,27 +63,78 @@
}
func Check(mainLicenseSpdxID string, config *ConfigDeps) error {
+ matrix := matrices[mainLicenseSpdxID]
+
report := Report{}
if err := Resolve(config, &report); err != nil {
return nil
}
- matrix := matrices[mainLicenseSpdxID]
+ return CheckWithMatrix(mainLicenseSpdxID, &matrix, &report)
+}
+
+func CheckWithMatrix(mainLicenseSpdxID string, matrix *CompatibilityMatrix, report *Report) error {
var incompatibleResults []*Result
for _, result := range append(report.Resolved, report.Skipped...) {
- compare := func(list []string) bool {
+ compare := func(list []string, spdxID string) bool {
for _, com := range list {
- if result.LicenseSpdxID == com {
+ if spdxID == com {
return true
}
}
return false
}
- if compatible := compare(matrix.Compatible); compatible {
- continue
+ compareAll := func(spdxIDs []string, compare func(spdxID string) bool) bool {
+ for _, spdxID := range spdxIDs {
+ if !compare(spdxID) {
+ return false
+ }
+ }
+ return true
}
- if incompatible := compare(matrix.Incompatible); incompatible {
- incompatibleResults = append(incompatibleResults, result)
+ compareAny := func(spdxIDs []string, compare func(spdxID string) bool) bool {
+ for _, spdxID := range spdxIDs {
+ if compare(spdxID) {
+ return true
+ }
+ }
+ return false
+ }
+
+ operator, spdxIDs := parseLicenseExpression(result.LicenseSpdxID)
+
+ switch operator {
+ case LicenseOperatorAND:
+ if compareAll(spdxIDs, func(spdxID string) bool {
+ return compare(matrix.Compatible, spdxID)
+ }) {
+ continue
+ }
+ if compareAny(spdxIDs, func(spdxID string) bool {
+ return compare(matrix.Incompatible, spdxID)
+ }) {
+ incompatibleResults = append(incompatibleResults, result)
+ }
+
+ case LicenseOperatorOR:
+ if compareAny(spdxIDs, func(spdxID string) bool {
+ return compare(matrix.Compatible, spdxID)
+ }) {
+ continue
+ }
+ if compareAll(spdxIDs, func(spdxID string) bool {
+ return compare(matrix.Incompatible, spdxID)
+ }) {
+ incompatibleResults = append(incompatibleResults, result)
+ }
+
+ default:
+ if compatible := compare(matrix.Compatible, spdxIDs[0]); compatible {
+ continue
+ }
+ if incompatible := compare(matrix.Incompatible, spdxIDs[0]); incompatible {
+ incompatibleResults = append(incompatibleResults, result)
+ }
}
}
@@ -88,3 +148,25 @@
return nil
}
+
+func parseLicenseExpression(s string) (operator LicenseOperator, spdxIDs []string) {
+ if ss := strings.Split(s, " AND "); len(ss) > 1 {
+ return LicenseOperatorAND, ss
+ }
+ if ss := strings.Split(s, " and "); len(ss) > 1 {
+ return LicenseOperatorAND, ss
+ }
+ if ss := strings.Split(s, " OR "); len(ss) > 1 {
+ return LicenseOperatorOR, ss
+ }
+ if ss := strings.Split(s, " or "); len(ss) > 1 {
+ return LicenseOperatorOR, ss
+ }
+ if ss := strings.Split(s, " WITH "); len(ss) > 1 {
+ return LicenseOperatorWITH, ss
+ }
+ if ss := strings.Split(s, " with "); len(ss) > 1 {
+ return LicenseOperatorWITH, ss
+ }
+ return LicenseOperatorNone, []string{s}
+}
diff --git a/pkg/deps/check_test.go b/pkg/deps/check_test.go
new file mode 100644
index 0000000..2ba66a4
--- /dev/null
+++ b/pkg/deps/check_test.go
@@ -0,0 +1,148 @@
+// 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_test
+
+import (
+ "github.com/apache/skywalking-eyes/pkg/deps"
+ "strings"
+ "testing"
+)
+
+var TestMatrix = deps.CompatibilityMatrix{
+ Compatible: []string{
+ "Apache-2.0",
+ "PHP-3.01",
+ "BSD-3-Clause",
+ "BSD-2-Clause",
+ "PostgreSQL",
+ "EPL-1.0",
+ "ISC",
+ },
+ Incompatible: []string{
+ "Unknown",
+ "LGPL-2.0+",
+ "LGPL-2.0",
+ "LGPL-2.0-only",
+ "LGPL-2.0-or-later",
+ "LGPL-2.1+",
+ "LGPL-2.1",
+ "LGPL-2.1-only",
+ "LGPL-2.1-or-later",
+ "LGPL-3.0+",
+ "LGPL-3.0",
+ "GPL-3.0+",
+ "GPL-3.0",
+ "GPL-2.0+",
+ "GPL-2.0",
+ "GPL-2.0-only",
+ "GPL-2.0-or-later",
+ },
+}
+
+func TestCheckWithMatrix(t *testing.T) {
+ if err := deps.CheckWithMatrix("Apache-2.0", &TestMatrix, &deps.Report{
+ Resolved: []*deps.Result{
+ {
+ Dependency: "Foo",
+ LicenseSpdxID: "Apache-2.0",
+ },
+ },
+ }); err != nil {
+ t.Errorf("Shouldn't return error")
+ }
+
+ if err := deps.CheckWithMatrix("Apache-2.0", &TestMatrix, &deps.Report{
+ Resolved: []*deps.Result{
+ {
+ Dependency: "Foo",
+ LicenseSpdxID: "Apache-2.0",
+ },
+ {
+ Dependency: "Bar",
+ LicenseSpdxID: "LGPL-2.0",
+ },
+ },
+ }); err == nil {
+ t.Errorf("Should return error")
+ } else if !strings.Contains(err.Error(), "License: LGPL-2.0 Dependency: Bar") {
+ t.Errorf("Should return error and contains dependency Bar, now is `%s`", err.Error())
+ }
+
+ if err := deps.CheckWithMatrix("Apache-2.0", &TestMatrix, &deps.Report{
+ Resolved: []*deps.Result{
+ {
+ Dependency: "Foo",
+ LicenseSpdxID: "Apache-2.0",
+ },
+ },
+ Skipped: []*deps.Result{
+ {
+ Dependency: "Bar",
+ LicenseSpdxID: "Unknown",
+ },
+ },
+ }); err == nil {
+ t.Errorf("Should return error")
+ } else if !strings.Contains(err.Error(), "License: Unknown Dependency: Bar") {
+ t.Errorf("Should return error and has dependency Bar, now is `%s`", err.Error())
+ }
+
+ if err := deps.CheckWithMatrix("Apache-2.0", &TestMatrix, &deps.Report{
+ Resolved: []*deps.Result{
+ {
+ Dependency: "Foo",
+ LicenseSpdxID: "Apache-2.0 OR MIT",
+ },
+ },
+ }); err != nil {
+ t.Errorf("Shouldn't return error")
+ }
+
+ if err := deps.CheckWithMatrix("Apache-2.0", &TestMatrix, &deps.Report{
+ Resolved: []*deps.Result{
+ {
+ Dependency: "Foo",
+ LicenseSpdxID: "GPL-3.0 and GPL-3.0-or-later",
+ },
+ },
+ }); err == nil {
+ t.Errorf("Should return error")
+ }
+
+ if err := deps.CheckWithMatrix("Apache-2.0", &TestMatrix, &deps.Report{
+ Resolved: []*deps.Result{
+ {
+ Dependency: "Foo",
+ LicenseSpdxID: "LGPL-2.1-only AND MIT AND BSD-2-Clause",
+ },
+ },
+ }); err == nil {
+ t.Errorf("Should return error")
+ }
+
+ if err := deps.CheckWithMatrix("Apache-2.0", &TestMatrix, &deps.Report{
+ Resolved: []*deps.Result{
+ {
+ Dependency: "Foo",
+ LicenseSpdxID: "GPL-2.0-or-later WITH Bison-exception-2.2",
+ },
+ },
+ }); err == nil {
+ t.Errorf("Should return error")
+ }
+}