mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
feat: Add R cataloger (#1790)
Add a cataloger that detects installed R packages by looking for DESCRIPTION files. The base R package is now picked up in coverageImage tests in test/cli/packages_cmd_test.go, so increment expected package counts for the tests that use that image. Signed-off-by: Will Murphy <will.murphy@anchore.com>
This commit is contained in:
parent
0580328ad9
commit
da3624644a
@ -6,5 +6,5 @@ const (
|
|||||||
|
|
||||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||||
JSONSchemaVersion = "7.1.5"
|
JSONSchemaVersion = "7.1.6"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -29,6 +29,9 @@ can be extended to include specific package metadata struct shapes in the future
|
|||||||
// not matter as long as it is exported.
|
// not matter as long as it is exported.
|
||||||
|
|
||||||
// TODO: this should be generated from reflection of whats in the pkg package
|
// TODO: this should be generated from reflection of whats in the pkg package
|
||||||
|
// Should be created during generation below; use reflection's ability to
|
||||||
|
// create types at runtime.
|
||||||
|
// should be same name as struct minus metadata
|
||||||
type artifactMetadataContainer struct {
|
type artifactMetadataContainer struct {
|
||||||
Alpm pkg.AlpmMetadata
|
Alpm pkg.AlpmMetadata
|
||||||
Apk pkg.ApkMetadata
|
Apk pkg.ApkMetadata
|
||||||
@ -56,6 +59,7 @@ type artifactMetadataContainer struct {
|
|||||||
PythonPackage pkg.PythonPackageMetadata
|
PythonPackage pkg.PythonPackageMetadata
|
||||||
PythonPipfilelock pkg.PythonPipfileLockMetadata
|
PythonPipfilelock pkg.PythonPipfileLockMetadata
|
||||||
PythonRequirements pkg.PythonRequirementsMetadata
|
PythonRequirements pkg.PythonRequirementsMetadata
|
||||||
|
RDescriptionFile pkg.RDescriptionFileMetadata
|
||||||
Rebar pkg.RebarLockMetadata
|
Rebar pkg.RebarLockMetadata
|
||||||
Rpm pkg.RpmMetadata
|
Rpm pkg.RpmMetadata
|
||||||
RustCargo pkg.CargoPackageMetadata
|
RustCargo pkg.CargoPackageMetadata
|
||||||
|
|||||||
1863
schema/json/schema-7.1.6.json
Normal file
1863
schema/json/schema-7.1.6.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -52,6 +52,8 @@ func SourceInfo(p pkg.Package) string {
|
|||||||
answer = "acquired package info from linux kernel module files"
|
answer = "acquired package info from linux kernel module files"
|
||||||
case pkg.NixPkg:
|
case pkg.NixPkg:
|
||||||
answer = "acquired package info from nix store path"
|
answer = "acquired package info from nix store path"
|
||||||
|
case pkg.Rpkg:
|
||||||
|
answer = "acquired package info from R-package DESCRIPTION file"
|
||||||
default:
|
default:
|
||||||
answer = "acquired package info from the following paths"
|
answer = "acquired package info from the following paths"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -223,6 +223,14 @@ func Test_SourceInfo(t *testing.T) {
|
|||||||
"from nix store path",
|
"from nix store path",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: pkg.Package{
|
||||||
|
Type: pkg.Rpkg,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"acquired package info from R-package DESCRIPTION file",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
var pkgTypes []pkg.Type
|
var pkgTypes []pkg.Type
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg/cataloger/php"
|
"github.com/anchore/syft/syft/pkg/cataloger/php"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/portage"
|
"github.com/anchore/syft/syft/pkg/cataloger/portage"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/python"
|
"github.com/anchore/syft/syft/pkg/cataloger/python"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/r"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/rpm"
|
"github.com/anchore/syft/syft/pkg/cataloger/rpm"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/ruby"
|
"github.com/anchore/syft/syft/pkg/cataloger/ruby"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/rust"
|
"github.com/anchore/syft/syft/pkg/cataloger/rust"
|
||||||
@ -53,6 +54,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
|
|||||||
php.NewComposerInstalledCataloger(),
|
php.NewComposerInstalledCataloger(),
|
||||||
portage.NewPortageCataloger(),
|
portage.NewPortageCataloger(),
|
||||||
python.NewPythonPackageCataloger(),
|
python.NewPythonPackageCataloger(),
|
||||||
|
r.NewPackageCataloger(),
|
||||||
rpm.NewRpmDBCataloger(),
|
rpm.NewRpmDBCataloger(),
|
||||||
ruby.NewGemSpecCataloger(),
|
ruby.NewGemSpecCataloger(),
|
||||||
sbom.NewSBOMCataloger(),
|
sbom.NewSBOMCataloger(),
|
||||||
@ -121,6 +123,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
|
|||||||
portage.NewPortageCataloger(),
|
portage.NewPortageCataloger(),
|
||||||
python.NewPythonIndexCataloger(),
|
python.NewPythonIndexCataloger(),
|
||||||
python.NewPythonPackageCataloger(),
|
python.NewPythonPackageCataloger(),
|
||||||
|
r.NewPackageCataloger(),
|
||||||
rpm.NewFileCataloger(),
|
rpm.NewFileCataloger(),
|
||||||
rpm.NewRpmDBCataloger(),
|
rpm.NewRpmDBCataloger(),
|
||||||
ruby.NewGemFileLockCataloger(),
|
ruby.NewGemFileLockCataloger(),
|
||||||
|
|||||||
13
syft/pkg/cataloger/r/cataloger.go
Normal file
13
syft/pkg/cataloger/r/cataloger.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package r
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||||
|
)
|
||||||
|
|
||||||
|
const catalogerName = "r-package-cataloger"
|
||||||
|
|
||||||
|
// NewPackageCataloger returns a new R cataloger object based on detection of R package DESCRIPTION files.
|
||||||
|
func NewPackageCataloger() *generic.Cataloger {
|
||||||
|
return generic.NewCataloger(catalogerName).
|
||||||
|
WithParserByGlobs(parseDescriptionFile, "**/DESCRIPTION")
|
||||||
|
}
|
||||||
60
syft/pkg/cataloger/r/cataloger_test.go
Normal file
60
syft/pkg/cataloger/r/cataloger_test.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package r
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/artifact"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRPackageCataloger(t *testing.T) {
|
||||||
|
expectedPkgs := []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "base",
|
||||||
|
Version: "4.3.0",
|
||||||
|
FoundBy: "r-package-cataloger",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("base/DESCRIPTION")),
|
||||||
|
Licenses: []string{"Part of R 4.3.0"},
|
||||||
|
Language: pkg.R,
|
||||||
|
Type: pkg.Rpkg,
|
||||||
|
PURL: "pkg:cran/base@4.3.0",
|
||||||
|
MetadataType: pkg.RDescriptionFileMetadataType,
|
||||||
|
Metadata: pkg.RDescriptionFileMetadata{
|
||||||
|
Title: "The R Base Package",
|
||||||
|
Description: "Base R functions.",
|
||||||
|
Author: "R Core Team and contributors worldwide",
|
||||||
|
Maintainer: "R Core Team <do-use-Contact-address@r-project.org>",
|
||||||
|
Built: "R 4.3.0; ; 2023-04-21 11:33:09 UTC; unix",
|
||||||
|
Suggests: []string{"methods"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "stringr",
|
||||||
|
Version: "1.5.0.9000",
|
||||||
|
FoundBy: "r-package-cataloger",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("stringr/DESCRIPTION")),
|
||||||
|
Licenses: []string{"MIT + file LICENSE"},
|
||||||
|
Language: pkg.R,
|
||||||
|
Type: pkg.Rpkg,
|
||||||
|
PURL: "pkg:cran/stringr@1.5.0.9000",
|
||||||
|
MetadataType: pkg.RDescriptionFileMetadataType,
|
||||||
|
Metadata: pkg.RDescriptionFileMetadata{
|
||||||
|
Title: "Simple, Consistent Wrappers for Common String Operations",
|
||||||
|
Description: "A consistent, simple and easy to use set of wrappers around the fantastic 'stringi' package. All function and argument names (and positions) are consistent, all functions deal with \"NA\"'s and zero length vectors in the same way, and the output from one function is easy to feed into the input of another.",
|
||||||
|
URL: []string{"https://stringr.tidyverse.org", "https://github.com/tidyverse/stringr"},
|
||||||
|
Imports: []string{
|
||||||
|
"cli", "glue (>= 1.6.1)", "lifecycle (>= 1.0.3)", "magrittr",
|
||||||
|
"rlang (>= 1.0.0)", "stringi (>= 1.5.3)", "vctrs (>= 0.4.0)",
|
||||||
|
},
|
||||||
|
Depends: []string{"R (>= 3.3)"},
|
||||||
|
Suggests: []string{"covr", "dplyr", "gt", "htmltools", "htmlwidgets", "knitr", "rmarkdown", "testthat (>= 3.0.0)", "tibble"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// TODO: relationships are not under test yet
|
||||||
|
var expectedRelationships []artifact.Relationship
|
||||||
|
|
||||||
|
pkgtest.NewCatalogTester().FromDirectory(t, "test-fixtures/installed").Expects(expectedPkgs, expectedRelationships).TestCataloger(t, NewPackageCataloger())
|
||||||
|
}
|
||||||
32
syft/pkg/cataloger/r/package.go
Normal file
32
syft/pkg/cataloger/r/package.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package r
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anchore/packageurl-go"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newPackage(pd parseData, locations ...source.Location) pkg.Package {
|
||||||
|
locationSet := source.NewLocationSet()
|
||||||
|
for _, loc := range locations {
|
||||||
|
locationSet.Add(loc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
|
||||||
|
}
|
||||||
|
result := pkg.Package{
|
||||||
|
Name: pd.Package,
|
||||||
|
Version: pd.Version,
|
||||||
|
Locations: locationSet,
|
||||||
|
Licenses: []string{pd.License},
|
||||||
|
Language: pkg.R,
|
||||||
|
Type: pkg.Rpkg,
|
||||||
|
PURL: packageURL(pd),
|
||||||
|
MetadataType: pkg.RDescriptionFileMetadataType,
|
||||||
|
Metadata: pd.RDescriptionFileMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
result.SetID()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func packageURL(m parseData) string {
|
||||||
|
return packageurl.NewPackageURL("cran", "", m.Package, m.Version, nil, "").ToString()
|
||||||
|
}
|
||||||
14
syft/pkg/cataloger/r/package_test.go
Normal file
14
syft/pkg/cataloger/r/package_test.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package r
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func Test_newPackage(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
}{}
|
||||||
|
|
||||||
|
for _, tt := range testCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
147
syft/pkg/cataloger/r/parse_description.go
Normal file
147
syft/pkg/cataloger/r/parse_description.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package r
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/artifact"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* some examples of license strings found in DESCRIPTION files:
|
||||||
|
find /usr/local/lib/R -name DESCRIPTION | xargs cat | grep 'License:' | sort | uniq
|
||||||
|
License: GPL
|
||||||
|
License: GPL (>= 2)
|
||||||
|
License: GPL (>=2)
|
||||||
|
License: GPL(>=2)
|
||||||
|
License: GPL (>= 2) | file LICENCE
|
||||||
|
License: GPL-2 | GPL-3
|
||||||
|
License: GPL-3
|
||||||
|
License: LGPL (>= 2)
|
||||||
|
License: LGPL (>= 2.1)
|
||||||
|
License: MIT + file LICENSE
|
||||||
|
License: Part of R 4.3.0
|
||||||
|
License: Unlimited
|
||||||
|
*/
|
||||||
|
|
||||||
|
func parseDescriptionFile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
|
values := extractFieldsFromDescriptionFile(reader)
|
||||||
|
m := parseDataFromDescriptionMap(values)
|
||||||
|
p := newPackage(m, []source.Location{reader.Location}...)
|
||||||
|
if p.Name == "" || p.Version == "" {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
return []pkg.Package{p}, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type parseData struct {
|
||||||
|
Package string
|
||||||
|
Version string
|
||||||
|
License string
|
||||||
|
pkg.RDescriptionFileMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDataFromDescriptionMap(values map[string]string) parseData {
|
||||||
|
return parseData{
|
||||||
|
License: values["License"],
|
||||||
|
Package: values["Package"],
|
||||||
|
Version: values["Version"],
|
||||||
|
RDescriptionFileMetadata: pkg.RDescriptionFileMetadata{
|
||||||
|
Title: values["Title"],
|
||||||
|
Description: cleanMultiLineValue(values["Description"]),
|
||||||
|
Maintainer: values["Maintainer"],
|
||||||
|
URL: commaSeparatedList(values["URL"]),
|
||||||
|
Depends: commaSeparatedList(values["Depends"]),
|
||||||
|
Imports: commaSeparatedList(values["Imports"]),
|
||||||
|
Suggests: commaSeparatedList(values["Suggests"]),
|
||||||
|
NeedsCompilation: yesNoToBool(values["NeedsCompilation"]),
|
||||||
|
Author: values["Author"],
|
||||||
|
Repository: values["Repository"],
|
||||||
|
Built: values["Built"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func yesNoToBool(s string) bool {
|
||||||
|
/*
|
||||||
|
$ docker run --rm -it rocker/r-ver bash
|
||||||
|
$ install2.r ggplot2 dplyr mlr3 caret # just some packages for a larger sample
|
||||||
|
$ find /usr/local/lib/R -name DESCRIPTION | xargs cat | grep 'NeedsCompilation:' | sort | uniq
|
||||||
|
NeedsCompilation: no
|
||||||
|
NeedsCompilation: yes
|
||||||
|
$ find /usr/local/lib/R -name DESCRIPTION | xargs cat | grep 'NeedsCompilation:' | wc -l
|
||||||
|
105
|
||||||
|
*/
|
||||||
|
return strings.EqualFold(s, "yes")
|
||||||
|
}
|
||||||
|
|
||||||
|
func commaSeparatedList(s string) []string {
|
||||||
|
var result []string
|
||||||
|
split := strings.Split(s, ",")
|
||||||
|
for _, piece := range split {
|
||||||
|
value := strings.TrimSpace(piece)
|
||||||
|
if value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, value)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
var space = regexp.MustCompile(`\s+`)
|
||||||
|
|
||||||
|
func cleanMultiLineValue(s string) string {
|
||||||
|
return space.ReplaceAllString(s, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractFieldsFromDescriptionFile(reader io.Reader) map[string]string {
|
||||||
|
result := make(map[string]string)
|
||||||
|
key := ""
|
||||||
|
var valueFragment strings.Builder
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
// line is like Key: Value -> start capturing value; close out previous value
|
||||||
|
// line is like \t\t continued value -> append to existing value
|
||||||
|
if len(line) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if startsWithWhitespace(line) {
|
||||||
|
// we're continuing a value
|
||||||
|
if key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
valueFragment.WriteByte('\n')
|
||||||
|
valueFragment.WriteString(strings.TrimSpace(line))
|
||||||
|
} else {
|
||||||
|
if key != "" {
|
||||||
|
// capture previous value
|
||||||
|
result[key] = valueFragment.String()
|
||||||
|
key = ""
|
||||||
|
valueFragment = strings.Builder{}
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(line, ":", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key = parts[0]
|
||||||
|
valueFragment.WriteString(strings.TrimSpace(parts[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if key != "" {
|
||||||
|
result[key] = valueFragment.String()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func startsWithWhitespace(s string) bool {
|
||||||
|
if s == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s[0] == ' ' || s[0] == '\t'
|
||||||
|
}
|
||||||
131
syft/pkg/cataloger/r/parse_description_test.go
Normal file
131
syft/pkg/cataloger/r/parse_description_test.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package r
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parseDescriptionFile(t *testing.T) {
|
||||||
|
type packageAssertions []func(*testing.T, []pkg.Package)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
assertions packageAssertions
|
||||||
|
fixture string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no package is returned if no version found",
|
||||||
|
fixture: filepath.Join("test-fixtures", "map-parse", "no-version"),
|
||||||
|
assertions: packageAssertions{
|
||||||
|
func(t *testing.T, p []pkg.Package) {
|
||||||
|
assert.Empty(t, p)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no package is returned if no package name found",
|
||||||
|
fixture: filepath.Join("test-fixtures", "map-parse", "no-name"),
|
||||||
|
assertions: packageAssertions{
|
||||||
|
func(t *testing.T, p []pkg.Package) {
|
||||||
|
assert.Empty(t, p)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "package return if both name and version found",
|
||||||
|
fixture: filepath.Join("test-fixtures", "map-parse", "simple"),
|
||||||
|
assertions: packageAssertions{
|
||||||
|
func(t *testing.T, p []pkg.Package) {
|
||||||
|
assert.Equal(t, 1, len(p))
|
||||||
|
assert.Equal(t, "base", p[0].Name)
|
||||||
|
assert.Equal(t, "4.3.0", p[0].Version)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
f, err := os.Open(tt.fixture)
|
||||||
|
input := source.LocationReadCloser{
|
||||||
|
Location: source.NewLocation(tt.fixture),
|
||||||
|
ReadCloser: f,
|
||||||
|
}
|
||||||
|
got, _, err := parseDescriptionFile(nil, nil, input)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
for _, assertion := range tt.assertions {
|
||||||
|
assertion(t, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_extractFieldsFromDescriptionFile(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fixture string
|
||||||
|
want map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "go case",
|
||||||
|
fixture: "test-fixtures/map-parse/simple",
|
||||||
|
want: map[string]string{
|
||||||
|
"Package": "base",
|
||||||
|
"Version": "4.3.0",
|
||||||
|
"Suggests": "methods",
|
||||||
|
"Built": "R 4.3.0; ; 2023-04-21 11:33:09 UTC; unix",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad cases",
|
||||||
|
fixture: "test-fixtures/map-parse/bad",
|
||||||
|
want: map[string]string{
|
||||||
|
"Key": "",
|
||||||
|
"Whitespace": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiline key-value",
|
||||||
|
fixture: "test-fixtures/map-parse/multiline",
|
||||||
|
want: map[string]string{
|
||||||
|
"Description": `A consistent, simple and easy to use set of wrappers around
|
||||||
|
the fantastic 'stringi' package. All function and argument names (and
|
||||||
|
positions) are consistent, all functions deal with "NA"'s and zero
|
||||||
|
length vectors in the same way, and the output from one function is
|
||||||
|
easy to feed into the input of another.`,
|
||||||
|
"License": "MIT + file LICENSE",
|
||||||
|
"Key": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "eof multiline",
|
||||||
|
fixture: "test-fixtures/map-parse/eof-multiline",
|
||||||
|
want: map[string]string{
|
||||||
|
"License": "MIT + file LICENSE",
|
||||||
|
"Description": `A consistent, simple and easy to use set of wrappers around
|
||||||
|
the fantastic 'stringi' package. All function and argument names (and
|
||||||
|
positions) are consistent, all functions deal with "NA"'s and zero
|
||||||
|
length vectors in the same way, and the output from one function is
|
||||||
|
easy to feed into the input of another.`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
file, err := os.Open(test.fixture)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
result := extractFieldsFromDescriptionFile(file)
|
||||||
|
|
||||||
|
assert.Equal(t, test.want, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
46
syft/pkg/cataloger/r/test-fixtures/DESCRIPTION
Normal file
46
syft/pkg/cataloger/r/test-fixtures/DESCRIPTION
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
Package: stringr
|
||||||
|
Title: Simple, Consistent Wrappers for Common String Operations
|
||||||
|
Version: 1.5.0.9000
|
||||||
|
Authors@R:
|
||||||
|
c(person(given = "Hadley",
|
||||||
|
family = "Wickham",
|
||||||
|
role = c("aut", "cre", "cph"),
|
||||||
|
email = "hadley@rstudio.com"),
|
||||||
|
person(given = "RStudio",
|
||||||
|
role = c("cph", "fnd")))
|
||||||
|
Description: A consistent, simple and easy to use set of wrappers around
|
||||||
|
the fantastic 'stringi' package. All function and argument names (and
|
||||||
|
positions) are consistent, all functions deal with "NA"'s and zero
|
||||||
|
length vectors in the same way, and the output from one function is
|
||||||
|
easy to feed into the input of another.
|
||||||
|
License: MIT + file LICENSE
|
||||||
|
URL: https://stringr.tidyverse.org, https://github.com/tidyverse/stringr
|
||||||
|
BugReports: https://github.com/tidyverse/stringr/issues
|
||||||
|
Depends:
|
||||||
|
R (>= 3.3)
|
||||||
|
Imports:
|
||||||
|
cli,
|
||||||
|
glue (>= 1.6.1),
|
||||||
|
lifecycle (>= 1.0.3),
|
||||||
|
magrittr,
|
||||||
|
rlang (>= 1.0.0),
|
||||||
|
stringi (>= 1.5.3),
|
||||||
|
vctrs (>= 0.4.0)
|
||||||
|
Suggests:
|
||||||
|
covr,
|
||||||
|
dplyr,
|
||||||
|
gt,
|
||||||
|
htmltools,
|
||||||
|
htmlwidgets,
|
||||||
|
knitr,
|
||||||
|
rmarkdown,
|
||||||
|
testthat (>= 3.0.0),
|
||||||
|
tibble
|
||||||
|
VignetteBuilder:
|
||||||
|
knitr
|
||||||
|
Config/Needs/website: tidyverse/tidytemplate
|
||||||
|
Config/testthat/edition: 3
|
||||||
|
Encoding: UTF-8
|
||||||
|
LazyData: true
|
||||||
|
Roxygen: list(markdown = TRUE)
|
||||||
|
RoxygenNote: 7.2.1
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
Package: base
|
||||||
|
Version: 4.3.0
|
||||||
|
Priority: base
|
||||||
|
Title: The R Base Package
|
||||||
|
Author: R Core Team and contributors worldwide
|
||||||
|
Maintainer: R Core Team <do-use-Contact-address@r-project.org>
|
||||||
|
Contact: R-help mailing list <r-help@r-project.org>
|
||||||
|
Description: Base R functions.
|
||||||
|
License: Part of R 4.3.0
|
||||||
|
Suggests: methods
|
||||||
|
Built: R 4.3.0; ; 2023-04-21 11:33:09 UTC; unix
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
Package: stringr
|
||||||
|
Title: Simple, Consistent Wrappers for Common String Operations
|
||||||
|
Version: 1.5.0.9000
|
||||||
|
Authors@R:
|
||||||
|
c(person(given = "Hadley",
|
||||||
|
family = "Wickham",
|
||||||
|
role = c("aut", "cre", "cph"),
|
||||||
|
email = "hadley@rstudio.com"),
|
||||||
|
person(given = "RStudio",
|
||||||
|
role = c("cph", "fnd")))
|
||||||
|
Description: A consistent, simple and easy to use set of wrappers around
|
||||||
|
the fantastic 'stringi' package. All function and argument names (and
|
||||||
|
positions) are consistent, all functions deal with "NA"'s and zero
|
||||||
|
length vectors in the same way, and the output from one function is
|
||||||
|
easy to feed into the input of another.
|
||||||
|
License: MIT + file LICENSE
|
||||||
|
URL: https://stringr.tidyverse.org, https://github.com/tidyverse/stringr
|
||||||
|
BugReports: https://github.com/tidyverse/stringr/issues
|
||||||
|
Depends:
|
||||||
|
R (>= 3.3)
|
||||||
|
Imports:
|
||||||
|
cli,
|
||||||
|
glue (>= 1.6.1),
|
||||||
|
lifecycle (>= 1.0.3),
|
||||||
|
magrittr,
|
||||||
|
rlang (>= 1.0.0),
|
||||||
|
stringi (>= 1.5.3),
|
||||||
|
vctrs (>= 0.4.0)
|
||||||
|
Suggests:
|
||||||
|
covr,
|
||||||
|
dplyr,
|
||||||
|
gt,
|
||||||
|
htmltools,
|
||||||
|
htmlwidgets,
|
||||||
|
knitr,
|
||||||
|
rmarkdown,
|
||||||
|
testthat (>= 3.0.0),
|
||||||
|
tibble
|
||||||
|
VignetteBuilder:
|
||||||
|
knitr
|
||||||
|
Config/Needs/website: tidyverse/tidytemplate
|
||||||
|
Config/testthat/edition: 3
|
||||||
|
Encoding: UTF-8
|
||||||
|
LazyData: true
|
||||||
|
Roxygen: list(markdown = TRUE)
|
||||||
|
RoxygenNote: 7.2.1
|
||||||
3
syft/pkg/cataloger/r/test-fixtures/map-parse/bad
Normal file
3
syft/pkg/cataloger/r/test-fixtures/map-parse/bad
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
MissingColon
|
||||||
|
Whitespace:
|
||||||
|
Key:
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
License: MIT + file LICENSE
|
||||||
|
Description: A consistent, simple and easy to use set of wrappers around
|
||||||
|
the fantastic 'stringi' package. All function and argument names (and
|
||||||
|
positions) are consistent, all functions deal with "NA"'s and zero
|
||||||
|
length vectors in the same way, and the output from one function is
|
||||||
|
easy to feed into the input of another.
|
||||||
8
syft/pkg/cataloger/r/test-fixtures/map-parse/multiline
Normal file
8
syft/pkg/cataloger/r/test-fixtures/map-parse/multiline
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Key: value
|
||||||
|
|
||||||
|
Description: A consistent, simple and easy to use set of wrappers around
|
||||||
|
the fantastic 'stringi' package. All function and argument names (and
|
||||||
|
positions) are consistent, all functions deal with "NA"'s and zero
|
||||||
|
length vectors in the same way, and the output from one function is
|
||||||
|
easy to feed into the input of another.
|
||||||
|
License: MIT + file LICENSE
|
||||||
2
syft/pkg/cataloger/r/test-fixtures/map-parse/no-name
Normal file
2
syft/pkg/cataloger/r/test-fixtures/map-parse/no-name
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Version: 1.2.3
|
||||||
|
Description: a package with no name
|
||||||
1
syft/pkg/cataloger/r/test-fixtures/map-parse/no-version
Normal file
1
syft/pkg/cataloger/r/test-fixtures/map-parse/no-version
Normal file
@ -0,0 +1 @@
|
|||||||
|
Package: foo
|
||||||
4
syft/pkg/cataloger/r/test-fixtures/map-parse/simple
Normal file
4
syft/pkg/cataloger/r/test-fixtures/map-parse/simple
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Package: base
|
||||||
|
Version: 4.3.0
|
||||||
|
Suggests: methods
|
||||||
|
Built: R 4.3.0; ; 2023-04-21 11:33:09 UTC; unix
|
||||||
@ -23,6 +23,7 @@ const (
|
|||||||
JavaScript Language = "javascript"
|
JavaScript Language = "javascript"
|
||||||
PHP Language = "php"
|
PHP Language = "php"
|
||||||
Python Language = "python"
|
Python Language = "python"
|
||||||
|
R Language = "R"
|
||||||
Ruby Language = "ruby"
|
Ruby Language = "ruby"
|
||||||
Rust Language = "rust"
|
Rust Language = "rust"
|
||||||
Swift Language = "swift"
|
Swift Language = "swift"
|
||||||
@ -41,6 +42,7 @@ var AllLanguages = []Language{
|
|||||||
JavaScript,
|
JavaScript,
|
||||||
PHP,
|
PHP,
|
||||||
Python,
|
Python,
|
||||||
|
R,
|
||||||
Ruby,
|
Ruby,
|
||||||
Rust,
|
Rust,
|
||||||
Swift,
|
Swift,
|
||||||
@ -91,6 +93,8 @@ func LanguageByName(name string) Language {
|
|||||||
// answer: no. We want this to definitively answer "which language does this package represent?"
|
// answer: no. We want this to definitively answer "which language does this package represent?"
|
||||||
// which might not be possible in all cases. See for more context: https://github.com/package-url/purl-spec/pull/178
|
// which might not be possible in all cases. See for more context: https://github.com/package-url/purl-spec/pull/178
|
||||||
return UnknownLanguage
|
return UnknownLanguage
|
||||||
|
case packageurl.TypeCran, "r":
|
||||||
|
return R
|
||||||
default:
|
default:
|
||||||
return UnknownLanguage
|
return UnknownLanguage
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,6 +66,10 @@ func TestLanguageFromPURL(t *testing.T) {
|
|||||||
purl: "pkg:hex/hpax/hpax@0.1.1",
|
purl: "pkg:hex/hpax/hpax@0.1.1",
|
||||||
want: UnknownLanguage,
|
want: UnknownLanguage,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
purl: "pkg:cran/base@4.3.0",
|
||||||
|
want: R,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var languages []string
|
var languages []string
|
||||||
@ -231,6 +235,10 @@ func TestLanguageByName(t *testing.T) {
|
|||||||
name: "haskell",
|
name: "haskell",
|
||||||
language: Haskell,
|
language: Haskell,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "R",
|
||||||
|
language: R,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|||||||
@ -38,6 +38,7 @@ const (
|
|||||||
PythonPipfileLockMetadataType MetadataType = "PythonPipfileLockMetadata"
|
PythonPipfileLockMetadataType MetadataType = "PythonPipfileLockMetadata"
|
||||||
PythonRequirementsMetadataType MetadataType = "PythonRequirementsMetadata"
|
PythonRequirementsMetadataType MetadataType = "PythonRequirementsMetadata"
|
||||||
RebarLockMetadataType MetadataType = "RebarLockMetadataType"
|
RebarLockMetadataType MetadataType = "RebarLockMetadataType"
|
||||||
|
RDescriptionFileMetadataType MetadataType = "RDescriptionFileMetadataType"
|
||||||
RpmMetadataType MetadataType = "RpmMetadata"
|
RpmMetadataType MetadataType = "RpmMetadata"
|
||||||
RustCargoPackageMetadataType MetadataType = "RustCargoPackageMetadata"
|
RustCargoPackageMetadataType MetadataType = "RustCargoPackageMetadata"
|
||||||
)
|
)
|
||||||
@ -69,6 +70,7 @@ var AllMetadataTypes = []MetadataType{
|
|||||||
PythonPackageMetadataType,
|
PythonPackageMetadataType,
|
||||||
PythonPipfileLockMetadataType,
|
PythonPipfileLockMetadataType,
|
||||||
PythonRequirementsMetadataType,
|
PythonRequirementsMetadataType,
|
||||||
|
RDescriptionFileMetadataType,
|
||||||
RebarLockMetadataType,
|
RebarLockMetadataType,
|
||||||
RpmMetadataType,
|
RpmMetadataType,
|
||||||
RustCargoPackageMetadataType,
|
RustCargoPackageMetadataType,
|
||||||
@ -101,6 +103,7 @@ var MetadataTypeByName = map[MetadataType]reflect.Type{
|
|||||||
PythonPackageMetadataType: reflect.TypeOf(PythonPackageMetadata{}),
|
PythonPackageMetadataType: reflect.TypeOf(PythonPackageMetadata{}),
|
||||||
PythonPipfileLockMetadataType: reflect.TypeOf(PythonPipfileLockMetadata{}),
|
PythonPipfileLockMetadataType: reflect.TypeOf(PythonPipfileLockMetadata{}),
|
||||||
PythonRequirementsMetadataType: reflect.TypeOf(PythonRequirementsMetadata{}),
|
PythonRequirementsMetadataType: reflect.TypeOf(PythonRequirementsMetadata{}),
|
||||||
|
RDescriptionFileMetadataType: reflect.TypeOf(RDescriptionFileMetadata{}),
|
||||||
RebarLockMetadataType: reflect.TypeOf(RebarLockMetadata{}),
|
RebarLockMetadataType: reflect.TypeOf(RebarLockMetadata{}),
|
||||||
RpmMetadataType: reflect.TypeOf(RpmMetadata{}),
|
RpmMetadataType: reflect.TypeOf(RpmMetadata{}),
|
||||||
RustCargoPackageMetadataType: reflect.TypeOf(CargoPackageMetadata{}),
|
RustCargoPackageMetadataType: reflect.TypeOf(CargoPackageMetadata{}),
|
||||||
|
|||||||
21
syft/pkg/r_package_metadata.go
Normal file
21
syft/pkg/r_package_metadata.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
type RDescriptionFileMetadata struct {
|
||||||
|
/*
|
||||||
|
Fields chosen by:
|
||||||
|
docker run --rm -it rocker/r-ver bash
|
||||||
|
$ install2.r ggplot2 # has a lot of dependencies
|
||||||
|
$ find /usr/local/lib/R -name DESCRIPTION | xargs cat | grep -v '^\s' | cut -d ':' -f 1 | sort | uniq -c | sort -nr
|
||||||
|
*/
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Author string `json:"author,omitempty"`
|
||||||
|
Maintainer string `json:"maintainer,omitempty"`
|
||||||
|
URL []string `json:"url,omitempty"`
|
||||||
|
Repository string `json:"repository,omitempty"`
|
||||||
|
Built string `json:"built,omitempty"`
|
||||||
|
NeedsCompilation bool `json:"needsCompilation,omitempty"`
|
||||||
|
Imports []string `json:"imports,omitempty"`
|
||||||
|
Depends []string `json:"depends,omitempty"`
|
||||||
|
Suggests []string `json:"suggests,omitempty"`
|
||||||
|
}
|
||||||
@ -33,6 +33,7 @@ const (
|
|||||||
PhpComposerPkg Type = "php-composer"
|
PhpComposerPkg Type = "php-composer"
|
||||||
PortagePkg Type = "portage"
|
PortagePkg Type = "portage"
|
||||||
PythonPkg Type = "python"
|
PythonPkg Type = "python"
|
||||||
|
Rpkg Type = "R-package"
|
||||||
RpmPkg Type = "rpm"
|
RpmPkg Type = "rpm"
|
||||||
RustPkg Type = "rust-crate"
|
RustPkg Type = "rust-crate"
|
||||||
)
|
)
|
||||||
@ -61,6 +62,7 @@ var AllPkgs = []Type{
|
|||||||
PhpComposerPkg,
|
PhpComposerPkg,
|
||||||
PortagePkg,
|
PortagePkg,
|
||||||
PythonPkg,
|
PythonPkg,
|
||||||
|
Rpkg,
|
||||||
RpmPkg,
|
RpmPkg,
|
||||||
RustPkg,
|
RustPkg,
|
||||||
}
|
}
|
||||||
@ -106,6 +108,8 @@ func (t Type) PackageURLType() string {
|
|||||||
return "nix"
|
return "nix"
|
||||||
case NpmPkg:
|
case NpmPkg:
|
||||||
return packageurl.TypeNPM
|
return packageurl.TypeNPM
|
||||||
|
case Rpkg:
|
||||||
|
return packageurl.TypeCran
|
||||||
case RpmPkg:
|
case RpmPkg:
|
||||||
return packageurl.TypeRPM
|
return packageurl.TypeRPM
|
||||||
case RustPkg:
|
case RustPkg:
|
||||||
@ -173,6 +177,8 @@ func TypeByName(name string) Type {
|
|||||||
return LinuxKernelModulePkg
|
return LinuxKernelModulePkg
|
||||||
case "nix":
|
case "nix":
|
||||||
return NixPkg
|
return NixPkg
|
||||||
|
case packageurl.TypeCran:
|
||||||
|
return Rpkg
|
||||||
default:
|
default:
|
||||||
return UnknownPkg
|
return UnknownPkg
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,6 +91,10 @@ func TestTypeFromPURL(t *testing.T) {
|
|||||||
purl: "pkg:nix/glibc@2.34?hash=h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
purl: "pkg:nix/glibc@2.34?hash=h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||||
expected: NixPkg,
|
expected: NixPkg,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
purl: "pkg:cran/base@4.3.0",
|
||||||
|
expected: Rpkg,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var pkgTypes []string
|
var pkgTypes []string
|
||||||
|
|||||||
@ -96,7 +96,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
|||||||
name: "squashed-scope-flag",
|
name: "squashed-scope-flag",
|
||||||
args: []string{"packages", "-o", "json", "-s", "squashed", coverageImage},
|
args: []string{"packages", "-o", "json", "-s", "squashed", coverageImage},
|
||||||
assertions: []traitAssertion{
|
assertions: []traitAssertion{
|
||||||
assertPackageCount(35),
|
assertPackageCount(36),
|
||||||
assertSuccessfulReturnCode,
|
assertSuccessfulReturnCode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -213,7 +213,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
|||||||
// the application config in the log matches that of what we expect to have been configured.
|
// the application config in the log matches that of what we expect to have been configured.
|
||||||
assertInOutput("parallelism: 2"),
|
assertInOutput("parallelism: 2"),
|
||||||
assertInOutput("parallelism=2"),
|
assertInOutput("parallelism=2"),
|
||||||
assertPackageCount(35),
|
assertPackageCount(36),
|
||||||
assertSuccessfulReturnCode,
|
assertSuccessfulReturnCode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -224,7 +224,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
|||||||
// the application config in the log matches that of what we expect to have been configured.
|
// the application config in the log matches that of what we expect to have been configured.
|
||||||
assertInOutput("parallelism: 1"),
|
assertInOutput("parallelism: 1"),
|
||||||
assertInOutput("parallelism=1"),
|
assertInOutput("parallelism=1"),
|
||||||
assertPackageCount(35),
|
assertPackageCount(36),
|
||||||
assertSuccessfulReturnCode,
|
assertSuccessfulReturnCode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -238,7 +238,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
|||||||
assertions: []traitAssertion{
|
assertions: []traitAssertion{
|
||||||
assertNotInOutput("secret_password"),
|
assertNotInOutput("secret_password"),
|
||||||
assertNotInOutput("secret_key_path"),
|
assertNotInOutput("secret_key_path"),
|
||||||
assertPackageCount(35),
|
assertPackageCount(36),
|
||||||
assertSuccessfulReturnCode,
|
assertSuccessfulReturnCode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -69,6 +69,14 @@ var imageOnlyTestCases = []testCase{
|
|||||||
"joda-time": "2.9.2",
|
"joda-time": "2.9.2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "find R packages",
|
||||||
|
pkgType: pkg.Rpkg,
|
||||||
|
pkgLanguage: pkg.R,
|
||||||
|
pkgInfo: map[string]string{
|
||||||
|
"base": "4.3.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var dirOnlyTestCases = []testCase{
|
var dirOnlyTestCases = []testCase{
|
||||||
|
|||||||
@ -220,10 +220,12 @@ func TestPkgCoverageDirectory(t *testing.T) {
|
|||||||
|
|
||||||
observedLanguages.Remove(pkg.UnknownLanguage.String())
|
observedLanguages.Remove(pkg.UnknownLanguage.String())
|
||||||
definedLanguages.Remove(pkg.UnknownLanguage.String())
|
definedLanguages.Remove(pkg.UnknownLanguage.String())
|
||||||
|
definedLanguages.Remove(pkg.R.String())
|
||||||
observedPkgs.Remove(string(pkg.UnknownPkg))
|
observedPkgs.Remove(string(pkg.UnknownPkg))
|
||||||
definedPkgs.Remove(string(pkg.BinaryPkg))
|
definedPkgs.Remove(string(pkg.BinaryPkg))
|
||||||
definedPkgs.Remove(string(pkg.LinuxKernelPkg))
|
definedPkgs.Remove(string(pkg.LinuxKernelPkg))
|
||||||
definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
|
definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
|
||||||
|
definedPkgs.Remove(string(pkg.Rpkg))
|
||||||
definedPkgs.Remove(string(pkg.UnknownPkg))
|
definedPkgs.Remove(string(pkg.UnknownPkg))
|
||||||
|
|
||||||
// for directory scans we should not expect to see any of the following package types
|
// for directory scans we should not expect to see any of the following package types
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
Package: base
|
||||||
|
Version: 4.3.0
|
||||||
|
Priority: base
|
||||||
|
Title: The R Base Package
|
||||||
|
Author: R Core Team and contributors worldwide
|
||||||
|
Maintainer: R Core Team <do-use-Contact-address@r-project.org>
|
||||||
|
Contact: R-help mailing list <r-help@r-project.org>
|
||||||
|
Description: Base R functions.
|
||||||
|
License: Part of R 4.3.0
|
||||||
|
Suggests: methods
|
||||||
|
Built: R 4.3.0; ; 2023-04-21 11:33:09 UTC; unix
|
||||||
Loading…
x
Reference in New Issue
Block a user