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

wusheng 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 0cb30eb  Add rust cargo support for dep command. (#121)
0cb30eb is described below

commit 0cb30eb950b94c5d2df93e881de0a7c2b9e57a69
Author: jmjoy <[email protected]>
AuthorDate: Thu Jul 7 22:56:23 2022 +0800

    Add rust cargo support for dep command. (#121)
    
    * Improve license comparation with operator.
    * Add rust cargo support.
---
 Dockerfile             |   2 +-
 pkg/deps/cargo.go      | 158 +++++++++++++++++++++++++++++++++++++++
 pkg/deps/cargo_test.go | 198 +++++++++++++++++++++++++++++++++++++++++++++++++
 pkg/deps/resolve.go    |   1 +
 4 files changed, 358 insertions(+), 1 deletion(-)

diff --git a/Dockerfile b/Dockerfile
index 1df4281..8024a92 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -32,7 +32,7 @@ COPY --from=build /license-eye/bin/linux/license-eye 
/bin/license-eye
 # Go
 COPY --from=build /usr/local/go/bin/go /usr/local/go/bin/go
 ENV PATH="/usr/local/go/bin:$PATH"
-RUN apk add --no-cache bash gcc musl-dev npm
+RUN apk add --no-cache bash gcc musl-dev npm cargo
 # Go
 
 WORKDIR /github/workspace/
diff --git a/pkg/deps/cargo.go b/pkg/deps/cargo.go
new file mode 100644
index 0000000..e2613d2
--- /dev/null
+++ b/pkg/deps/cargo.go
@@ -0,0 +1,158 @@
+// 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 (
+       "encoding/json"
+       "os"
+       "os/exec"
+       "path/filepath"
+       "regexp"
+
+       "github.com/apache/skywalking-eyes/internal/logger"
+       "github.com/apache/skywalking-eyes/pkg/license"
+)
+
+type CargoMetadata struct {
+       Packages []CargoPackage `json:"packages"`
+}
+
+type CargoPackage struct {
+       Name         string `json:"name"`
+       Version      string `json:"version"`
+       License      string `json:"license"`
+       LicenseFile  string `json:"license_file"`
+       ManifestPath string `json:"manifest_path"`
+}
+
+type CargoTomlResolver struct {
+       Resolver
+}
+
+func (resolver *CargoTomlResolver) CanResolve(file string) bool {
+       base := filepath.Base(file)
+       logger.Log.Debugln("Base name:", base)
+       return base == "Cargo.toml"
+}
+
+// Resolve resolves licenses of all dependencies declared in the Cargo.toml 
file.
+func (resolver *CargoTomlResolver) Resolve(cargoTomlFile string, config 
*ConfigDeps, report *Report) error {
+       dir := filepath.Dir(cargoTomlFile)
+
+       download := exec.Command("cargo", "fetch")
+       logger.Log.Debugf("Run command: %v, please wait", download.String())
+       download.Stdout = os.Stdout
+       download.Stderr = os.Stderr
+       download.Dir = dir
+       if err := download.Run(); err != nil {
+               return err
+       }
+
+       cmd := exec.Command("cargo", "metadata", "--format-version=1", 
"--all-features")
+       cmd.Dir = dir
+       output, err := cmd.Output()
+       if err != nil {
+               return err
+       }
+
+       var metadata CargoMetadata
+       if err := json.Unmarshal(output, &metadata); err != nil {
+               return err
+       }
+
+       logger.Log.Debugln("Package size:", len(metadata.Packages))
+
+       return resolver.ResolvePackages(metadata.Packages, config, report)
+}
+
+// ResolvePackages resolves the licenses of the given packages.
+func (resolver *CargoTomlResolver) ResolvePackages(packages []CargoPackage, 
config *ConfigDeps, report *Report) error {
+       for i := range packages {
+               pkg := packages[i]
+
+               if config.IsExcluded(pkg.Name, pkg.Version) {
+                       continue
+               }
+               if l, ok := config.GetUserConfiguredLicense(pkg.Name, 
pkg.Version); ok {
+                       report.Resolve(&Result{
+                               Dependency:    pkg.Name,
+                               LicenseSpdxID: l,
+                               Version:       pkg.Version,
+                       })
+                       continue
+               }
+               err := resolver.ResolvePackageLicense(config, &pkg, report)
+               if err != nil {
+                       logger.Log.Warnf("Failed to resolve the license of 
<%s@%s>: %v\n", pkg.Name, pkg.Version, err)
+                       report.Skip(&Result{
+                               Dependency:    pkg.Name,
+                               LicenseSpdxID: Unknown,
+                               Version:       pkg.Version,
+                       })
+               }
+       }
+       return nil
+}
+
+var cargoPossibleLicenseFileName = 
regexp.MustCompile(`(?i)^LICENSE|LICENCE(\.txt)?|LICENSE-.+|COPYING(\.txt)?$`)
+
+// ResolvePackageLicense resolve the package license.
+// The CargoPackage.LicenseFile is generally used for non-standard licenses 
and is ignored now.
+func (resolver *CargoTomlResolver) ResolvePackageLicense(config *ConfigDeps, 
pkg *CargoPackage, report *Report) error {
+       dir := filepath.Dir(pkg.ManifestPath)
+       logger.Log.Debugf("Directory of %+v is %+v", pkg.Name, dir)
+       files, err := os.ReadDir(dir)
+       if err != nil {
+               return nil
+       }
+
+       var licenseFilePath string
+       var licenseContent []byte
+
+       licenseID := pkg.License
+
+       for _, info := range files {
+               if !cargoPossibleLicenseFileName.MatchString(info.Name()) {
+                       continue
+               }
+
+               licenseFilePath = filepath.Join(dir, info.Name())
+               licenseContent, err = os.ReadFile(licenseFilePath)
+               if err != nil {
+                       return err
+               }
+
+               break
+       }
+
+       if licenseID == "" { // If pkg.License is empty, identify the license 
ID from the license file content
+               if licenseID, err = license.Identify(string(licenseContent), 
config.Threshold); err != nil {
+                       return err
+               }
+       }
+
+       report.Resolve(&Result{
+               Dependency:      pkg.Name,
+               LicenseFilePath: licenseFilePath,
+               LicenseContent:  string(licenseContent),
+               LicenseSpdxID:   licenseID,
+               Version:         pkg.Version,
+       })
+
+       return nil
+}
diff --git a/pkg/deps/cargo_test.go b/pkg/deps/cargo_test.go
new file mode 100644
index 0000000..db401a3
--- /dev/null
+++ b/pkg/deps/cargo_test.go
@@ -0,0 +1,198 @@
+// 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/internal/logger"
+       "github.com/apache/skywalking-eyes/pkg/deps"
+       "os"
+       "os/exec"
+       "path/filepath"
+       "testing"
+)
+
+func TestCanResolveCargo(t *testing.T) {
+       resolver := new(deps.CargoTomlResolver)
+       if !resolver.CanResolve("Cargo.toml") {
+               t.Error("CargoTomlResolver should resolve Cargo.toml")
+               return
+       }
+       if resolver.CanResolve("go.mod") {
+               t.Error("CargoTomlResolver shouldn't resolve go.mod")
+       }
+}
+
+func TestResolveCargos(t *testing.T) {
+       if _, err := exec.Command("cargo", "--version").Output(); err != nil {
+               logger.Log.Warnf("Failed to find cargo, the test 
`TestResolveCargo` was skipped")
+               return
+       }
+
+       {
+               cargoToml := `
+[package]
+name = "foo"
+version = "0.0.0"
+publish = false
+edition = "2021"
+license = "Apache-2.0"
+`
+
+               config := deps.ConfigDeps{
+                       Threshold: 0,
+                       Files:     []string{"Cargo.toml"},
+                       Licenses:  []*deps.ConfigDepLicense{},
+                       Excludes:  []deps.Exclude{},
+               }
+
+               report := resolveTmpCargo(t, cargoToml, &config)
+               if len(report.Resolved) != 1 {
+                       t.Error("len(report.Resolved) != 1")
+               }
+               if report.Resolved[0].LicenseSpdxID != "Apache-2.0" {
+                       t.Error("Package foo license isn't Apache-2.0")
+               }
+       }
+
+       {
+               cargoToml := `
+[package]
+name = "foo"
+version = "0.0.0"
+publish = false
+edition = "2021"
+license = "Apache-2.0"
+`
+
+               config := deps.ConfigDeps{
+                       Threshold: 0,
+                       Files:     []string{"Cargo.toml"},
+                       Licenses:  []*deps.ConfigDepLicense{},
+                       Excludes:  []deps.Exclude{{Name: "foo", Version: 
"0.0.0"}},
+               }
+
+               report := resolveTmpCargo(t, cargoToml, &config)
+               if len(report.Resolved) != 0 {
+                       t.Error("len(report.Resolved) != 0")
+               }
+       }
+
+       {
+               cargoToml := `
+[package]
+name = "foo"
+version = "0.0.0"
+publish = false
+edition = "2021"
+license = "Apache-2.0"
+`
+
+               config := deps.ConfigDeps{
+                       Threshold: 0,
+                       Files:     []string{},
+                       Licenses: []*deps.ConfigDepLicense{
+                               {
+                                       Name:    "foo",
+                                       Version: "0.0.0",
+                                       License: "MIT",
+                               },
+                       },
+                       Excludes: []deps.Exclude{},
+               }
+
+               report := resolveTmpCargo(t, cargoToml, &config)
+               if len(report.Resolved) != 1 {
+                       t.Error("len(report.Resolved) != 1")
+               }
+               if report.Resolved[0].LicenseSpdxID != "MIT" {
+                       t.Error("Package foo license isn't modified to  MIT")
+               }
+       }
+
+       {
+               cargoToml := `
+[package]
+name = "foo"
+version = "0.0.0"
+publish = false
+edition = "2021"
+license = "Apache-2.0"
+
+[dependencies]
+libc = "0.2.126"
+`
+
+               config := deps.ConfigDeps{
+                       Threshold: 0,
+                       Files:     []string{"Cargo.toml"},
+                       Licenses:  []*deps.ConfigDepLicense{},
+                       Excludes:  []deps.Exclude{},
+               }
+
+               report := resolveTmpCargo(t, cargoToml, &config)
+               if len(report.Resolved) != 2 {
+                       t.Error("len(report.Resolved) != 2")
+               }
+               for _, result := range report.Resolved {
+                       if result.Dependency == "libc" {
+                               if result.LicenseSpdxID != "MIT OR Apache-2.0" 
|| result.LicenseContent == "" {
+                                       t.Error("Resolve dependency libc 
failed")
+                               }
+                       }
+               }
+       }
+}
+
+func resolveTmpCargo(t *testing.T, cargoTomlContent string, config 
*deps.ConfigDeps) *deps.Report {
+       dir, err := os.MkdirTemp("", "skywalking-eyes-test-cargo-")
+       if err != nil {
+               t.Error("Make temp dir failed", err)
+               return nil
+       }
+       defer func(path string) {
+               err := os.RemoveAll(path)
+               if err != nil {
+                       logger.Log.Warn(err)
+               }
+       }(dir) // clean up
+
+       if err := os.Chdir(dir); err != nil {
+               t.Error("Chdir failed", err)
+               return nil
+       }
+
+       if _, err := exec.Command("cargo", "init", "--lib").Output(); err != 
nil {
+               t.Error("Cargo init failed", err)
+               return nil
+       }
+
+       cargoFile := filepath.Join(dir, "Cargo.toml")
+       if err := os.WriteFile(cargoFile, []byte(cargoTomlContent), 0644); err 
!= nil {
+               t.Error("Write Cargo.toml failed", err)
+               return nil
+       }
+
+       resolver := new(deps.CargoTomlResolver)
+
+       var report deps.Report
+       if err := resolver.Resolve(cargoFile, config, &report); err != nil {
+               t.Error("CargoTomlResolver resolve failed", err)
+               return nil
+       }
+       return &report
+}
diff --git a/pkg/deps/resolve.go b/pkg/deps/resolve.go
index 8769d79..fc2f562 100644
--- a/pkg/deps/resolve.go
+++ b/pkg/deps/resolve.go
@@ -31,6 +31,7 @@ var Resolvers = []Resolver{
        new(NpmResolver),
        new(MavenPomResolver),
        new(JarResolver),
+       new(CargoTomlResolver),
 }
 
 func Resolve(config *ConfigDeps, report *Report) error {

Reply via email to