mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
fix: fetch Dart package versions from sdk entries (#3572)
* fix: fetch Dart package versions from sdk entries Packages that are provided by an SDK, mainly Flutter, will have their version set to 0.0.0 in Dart's pubspec.lock file. Their actual version is linked to that SDK, which is defined either as a version range or a minimum supported version, rather than an explicit, single version. The pubspec.lock file has a dedicated section to define those SDK version range constraints, which is already stored internally when parsing the file itself. The solution now is to look up such a package's SDK name, retrieve the defined version range / lower version boundary, and set the minimum supported version as the package's new version. Signed-off-by: Sven Gregori <sven@craplab.fi> * Ignore Dart package if SDK version cannot be fetched Signed-off-by: Sven Gregori <sven@craplab.fi> * fix linting issues Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Sven Gregori <sven@craplab.fi> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
616c8dfe2a
commit
2846bb18d2
@ -4,8 +4,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
@ -68,7 +70,26 @@ func parsePubspecLock(_ context.Context, _ file.Resolver, _ *generic.Environment
|
||||
}
|
||||
|
||||
var names []string
|
||||
for name := range p.Packages {
|
||||
for name, pkg := range p.Packages {
|
||||
if pkg.Source == "sdk" && pkg.Version == "0.0.0" {
|
||||
// Packages that are delivered as part of an SDK (e.g. Flutter) have their
|
||||
// version set to "0.0.0" in the package definition. The actual version
|
||||
// should refer to the SDK version, which is defined in a dedicated section
|
||||
// in the pubspec.lock file and uses a version range constraint.
|
||||
//
|
||||
// If such a package is detected, look up the version range constraint of
|
||||
// its matching SDK, and set the minimum supported version as its new version.
|
||||
sdkName := pkg.Description.Name
|
||||
sdkVersion, err := p.getSdkVersion(sdkName)
|
||||
|
||||
if err != nil {
|
||||
log.Tracef("failed to resolve %s SDK version for package %s: %v", sdkName, name, err)
|
||||
continue
|
||||
}
|
||||
pkg.Version = sdkVersion
|
||||
p.Packages[name] = pkg
|
||||
}
|
||||
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
@ -89,6 +110,68 @@ func parsePubspecLock(_ context.Context, _ file.Resolver, _ *generic.Environment
|
||||
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||
}
|
||||
|
||||
// Look up the version range constraint for a given sdk name, if found,
|
||||
// and return its lowest supported version matching that constraint.
|
||||
//
|
||||
// The sdks and their constraints are defined in the pubspec.lock file, e.g.
|
||||
//
|
||||
// sdks:
|
||||
// dart: ">=2.12.0 <3.0.0"
|
||||
// flutter: ">=3.24.5"
|
||||
//
|
||||
// and stored in the pubspecLock.Sdks map during parsing.
|
||||
//
|
||||
// Example based on the data above:
|
||||
//
|
||||
// getSdkVersion("dart") -> "2.12.0"
|
||||
// getSdkVersion("flutter") -> "3.24.5"
|
||||
// getSdkVersion("undefined") -> error
|
||||
func (psl *pubspecLock) getSdkVersion(sdk string) (string, error) {
|
||||
constraint, found := psl.Sdks[sdk]
|
||||
|
||||
if !found {
|
||||
return "", fmt.Errorf("cannot find %s SDK", sdk)
|
||||
}
|
||||
|
||||
return parseMinimumSdkVersion(constraint)
|
||||
}
|
||||
|
||||
// Parse a given version range constraint and return its lowest supported version.
|
||||
//
|
||||
// This is intended for packages that are part of an SDK (e.g. Flutter) and don't
|
||||
// have an explicit version string set. This will take the given constraint
|
||||
// parameter, ensure it's a valid constraint string, and return the lowest version
|
||||
// within that constraint range.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// parseMinimumSdkVersion("^1.2.3") -> "1.2.3"
|
||||
// parseMinimumSdkVersion(">=1.2.3") -> "1.2.3"
|
||||
// parseMinimumSdkVersion(">=1.2.3 <2.0.0") -> "1.2.3"
|
||||
// parseMinimumSdkVersion("1.2.3") -> error
|
||||
//
|
||||
// see https://dart.dev/tools/pub/dependencies#version-constraints for the
|
||||
// constraint format used in Dart SDK defintions.
|
||||
func parseMinimumSdkVersion(constraint string) (string, error) {
|
||||
// Match strings that
|
||||
// 1. start with either "^" or ">=" (Dart SDK constraints only use those two)
|
||||
// 2. followed by a valid semantic version, matched as "version" named subexpression
|
||||
// 3. followed by a space (if there's a range) or end of string (if there's only a lower boundary)
|
||||
// |---1--||------------------2------------------||-3-|
|
||||
re := regexp.MustCompile(`^(\^|>=)(?P<version>` + semver.SemVerRegex + `)( |$)`)
|
||||
|
||||
if !re.MatchString(constraint) {
|
||||
return "", fmt.Errorf("unsupported or invalid constraint '%s'", constraint)
|
||||
}
|
||||
|
||||
// Read "version" subexpression (see 2. above) into version variable
|
||||
var version []byte
|
||||
matchIndex := re.FindStringSubmatchIndex(constraint)
|
||||
version = re.ExpandString(version, "$version", constraint, matchIndex)
|
||||
|
||||
return string(version), nil
|
||||
}
|
||||
|
||||
func (p *pubspecLockPackage) getVcsURL() string {
|
||||
if p.Source == "git" {
|
||||
if p.Description.Path == "." {
|
||||
|
||||
@ -3,6 +3,8 @@ package dart
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@ -76,14 +78,14 @@ func TestParsePubspecLock(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "flutter",
|
||||
Version: "0.0.0",
|
||||
PURL: "pkg:pub/flutter@0.0.0",
|
||||
Version: "3.24.5",
|
||||
PURL: "pkg:pub/flutter@3.24.5",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dart,
|
||||
Type: pkg.DartPubPkg,
|
||||
Metadata: pkg.DartPubspecLockEntry{
|
||||
Name: "flutter",
|
||||
Version: "0.0.0",
|
||||
Version: "3.24.5",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -113,3 +115,163 @@ func Test_corruptPubspecLock(t *testing.T) {
|
||||
WithError().
|
||||
TestParser(t, parsePubspecLock)
|
||||
}
|
||||
|
||||
func Test_missingSdkEntryPubspecLock(t *testing.T) {
|
||||
fixture := "test-fixtures/missing-sdk/pubspec.lock"
|
||||
fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))
|
||||
|
||||
// SDK version is missing, so flutter version cannot be determined and
|
||||
// is ignored, expecting args as only package in the list as a result.
|
||||
expected := []pkg.Package{
|
||||
{
|
||||
Name: "args",
|
||||
Version: "1.6.0",
|
||||
PURL: "pkg:pub/args@1.6.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dart,
|
||||
Type: pkg.DartPubPkg,
|
||||
Metadata: pkg.DartPubspecLockEntry{
|
||||
Name: "args",
|
||||
Version: "1.6.0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: relationships are not under test
|
||||
var expectedRelationships []artifact.Relationship
|
||||
|
||||
pkgtest.TestFileParser(t, fixture, parsePubspecLock, expected, expectedRelationships)
|
||||
}
|
||||
|
||||
func Test_invalidSdkEntryPubspecLock(t *testing.T) {
|
||||
fixture := "test-fixtures/invalid-sdk/pubspec.lock"
|
||||
fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))
|
||||
|
||||
// SDK version is invalid, so flutter version cannot be determined and
|
||||
// is ignored, expecting args as only package in the list as a result.
|
||||
expected := []pkg.Package{
|
||||
{
|
||||
Name: "args",
|
||||
Version: "1.6.0",
|
||||
PURL: "pkg:pub/args@1.6.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dart,
|
||||
Type: pkg.DartPubPkg,
|
||||
Metadata: pkg.DartPubspecLockEntry{
|
||||
Name: "args",
|
||||
Version: "1.6.0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: relationships are not under test
|
||||
var expectedRelationships []artifact.Relationship
|
||||
|
||||
pkgtest.TestFileParser(t, fixture, parsePubspecLock, expected, expectedRelationships)
|
||||
}
|
||||
|
||||
func Test_sdkVersionLookup(t *testing.T) {
|
||||
psl := &pubspecLock{
|
||||
Sdks: make(map[string]string, 5),
|
||||
}
|
||||
|
||||
psl.Sdks["minVersionSdk"] = ">=0.1.2"
|
||||
psl.Sdks["rangeVersionSdk"] = ">=1.2.3 <2.0.0"
|
||||
psl.Sdks["caretVersionSdk"] = "^2.3.4"
|
||||
psl.Sdks["emptyVersionSdk"] = ""
|
||||
psl.Sdks["invalidVersionSdk"] = "not a constraint"
|
||||
|
||||
var version string
|
||||
var err error
|
||||
|
||||
version, err = psl.getSdkVersion("minVersionSdk")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "0.1.2", version)
|
||||
|
||||
version, err = psl.getSdkVersion("rangeVersionSdk")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "1.2.3", version)
|
||||
|
||||
version, err = psl.getSdkVersion("caretVersionSdk")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "2.3.4", version)
|
||||
|
||||
version, err = psl.getSdkVersion("emptyVersionSdk")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "", version)
|
||||
|
||||
version, err = psl.getSdkVersion("invalidVersionSdk")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "", version)
|
||||
|
||||
version, err = psl.getSdkVersion("nonexistantSdk")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "", version)
|
||||
}
|
||||
|
||||
func Test_sdkVersionParser_valid(t *testing.T) {
|
||||
var version string
|
||||
var err error
|
||||
|
||||
// map constraints to expected version
|
||||
patterns := map[string]string{
|
||||
"^0.0.0": "0.0.0",
|
||||
">=0.0.0": "0.0.0",
|
||||
"^1.23.4": "1.23.4",
|
||||
">=1.23.4": "1.23.4",
|
||||
"^11.22.33": "11.22.33",
|
||||
">=11.22.33": "11.22.33",
|
||||
"^123.123456.12345678": "123.123456.12345678",
|
||||
">=123.123456.12345678": "123.123456.12345678",
|
||||
">=1.2.3 <2.3.4": "1.2.3",
|
||||
">=1.2.3 random string": "1.2.3",
|
||||
">=1.2.3 >=0.1.2": "1.2.3",
|
||||
"^1.2": "1.2",
|
||||
">=1.2": "1.2",
|
||||
"^1.2.3-rc4": "1.2.3-rc4",
|
||||
">=1.2.3-rc4": "1.2.3-rc4",
|
||||
"^2.34.5+hotfix6": "2.34.5+hotfix6",
|
||||
">=2.34.5+hotfix6": "2.34.5+hotfix6",
|
||||
}
|
||||
|
||||
for constraint, expected := range patterns {
|
||||
version, err = parseMinimumSdkVersion(constraint)
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, expected, version, "constraint '%s", constraint)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sdkVersionParser_invalid(t *testing.T) {
|
||||
var version string
|
||||
var err error
|
||||
|
||||
patterns := []string{
|
||||
"",
|
||||
"abc",
|
||||
"^abc",
|
||||
">=abc",
|
||||
"^a.b.c",
|
||||
">=a.b.c",
|
||||
"1.2.34",
|
||||
">1.2.34",
|
||||
"<=1.2.34",
|
||||
"<1.2.34",
|
||||
"^1.2.3.4",
|
||||
">=1.2.3.4",
|
||||
"^1.x.0",
|
||||
">=1.x.0",
|
||||
"^1x2x3",
|
||||
">=1x2x3",
|
||||
"^1.-2.3",
|
||||
">=1.-2.3",
|
||||
"abc <1.2.34",
|
||||
"^2.3.45hotfix6",
|
||||
">=2.3.45hotfix6",
|
||||
}
|
||||
|
||||
for _, pattern := range patterns {
|
||||
version, err = parseMinimumSdkVersion(pattern)
|
||||
assert.Error(t, err)
|
||||
assert.Equalf(t, "", version, "constraint '%s'", pattern)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
packages:
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
sdks:
|
||||
flutter: "3.24.5"
|
||||
@ -0,0 +1,13 @@
|
||||
packages:
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
@ -52,3 +52,4 @@ packages:
|
||||
version: "1.11.20"
|
||||
sdks:
|
||||
dart: ">=2.12.0 <3.0.0"
|
||||
flutter: ">=3.24.5"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user