Skip to content

Commit

Permalink
feat: gradle lockfile support (#1719)
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Sachs <[email protected]>
  • Loading branch information
henrysachs authored Apr 6, 2023
1 parent da44db9 commit 0fed17f
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 14 deletions.
2 changes: 2 additions & 0 deletions syft/pkg/cataloger/cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger {
java.NewJavaCataloger(cfg.Java()),
java.NewJavaPomCataloger(),
java.NewNativeImageCataloger(),
java.NewJavaGradleLockfileCataloger(),
apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(cfg.Go()),
golang.NewGoModFileCataloger(cfg.Go()),
Expand Down Expand Up @@ -107,6 +108,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
java.NewJavaCataloger(cfg.Java()),
java.NewJavaPomCataloger(),
java.NewNativeImageCataloger(),
java.NewJavaGradleLockfileCataloger(),
apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(cfg.Go()),
golang.NewGoModFileCataloger(cfg.Go()),
Expand Down
29 changes: 29 additions & 0 deletions syft/pkg/cataloger/java/archive_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,39 @@ func TestParseJar(t *testing.T) {
Manifest: &pkg.JavaManifest{
Main: map[string]string{
"Manifest-Version": "1.0",
"Main-Class": "hello.HelloWorld",
},
},
},
},
"joda-time": {
Name: "joda-time",
Version: "2.2",
PURL: "pkg:maven/joda-time/[email protected]",
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
// ensure that nested packages with different names than that of the parent are appended as
// a suffix on the virtual path
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar:joda-time",
PomProperties: &pkg.PomProperties{
Path: "META-INF/maven/joda-time/joda-time/pom.properties",
GroupID: "joda-time",
ArtifactID: "joda-time",
Version: "2.2",
},
PomProject: &pkg.PomProject{
Path: "META-INF/maven/joda-time/joda-time/pom.xml",
GroupID: "joda-time",
ArtifactID: "joda-time",
Version: "2.2",
Name: "Joda time",
Description: "Date and time library to replace JDK date handling",
URL: "http://joda-time.sourceforge.net",
},
},
},
},
},
{
Expand Down
8 changes: 8 additions & 0 deletions syft/pkg/cataloger/java/cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,11 @@ func NewJavaPomCataloger() *generic.Cataloger {
return generic.NewCataloger("java-pom-cataloger").
WithParserByGlobs(parserPomXML, "**/pom.xml")
}

// NewJavaGradleLockfileCataloger returns a cataloger capable of parsing
// dependencies from a gradle.lockfile file.
// older versions of lockfiles aren't supported yet
func NewJavaGradleLockfileCataloger() *generic.Cataloger {
return generic.NewCataloger("java-gradle-lockfile-cataloger").
WithParserByGlobs(parseGradleLockfile, gradleLockfileGlob)
}
63 changes: 63 additions & 0 deletions syft/pkg/cataloger/java/parse_gradle_lockfile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package java

import (
"bufio"
"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"
)

const gradleLockfileGlob = "**/gradle.lockfile*"

// Dependency represents a single dependency in the gradle.lockfile file
type LockfileDependency struct {
Group string
Name string
Version string
}

func parseGradleLockfile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package

// Create a new scanner to read the file
scanner := bufio.NewScanner(reader)

// Create slices to hold the dependencies and plugins
dependencies := []LockfileDependency{}

// Loop over all lines in the file
for scanner.Scan() {
line := scanner.Text()

// Trim leading and trailing whitespace from the line
line = strings.TrimSpace(line)

groupNameVersion := line
groupNameVersion = strings.Split(groupNameVersion, "=")[0]
parts := strings.Split(groupNameVersion, ":")

// we have a version directly specified
if len(parts) == 3 {
// Create a new Dependency struct and add it to the dependencies slice
dep := LockfileDependency{Group: parts[0], Name: parts[1], Version: parts[2]}
dependencies = append(dependencies, dep)
}
}
// map the dependencies
for _, dep := range dependencies {
mappedPkg := pkg.Package{
Name: dep.Name,
Version: dep.Version,
Locations: source.NewLocationSet(reader.Location),
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
}
pkgs = append(pkgs, mappedPkg)
}

return pkgs, nil, nil
}
52 changes: 52 additions & 0 deletions syft/pkg/cataloger/java/parse_gradle_lockfile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package java

import (
"testing"

"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
"github.com/anchore/syft/syft/source"
)

func Test_parserGradleLockfile(t *testing.T) {
tests := []struct {
input string
expected []pkg.Package
}{
{
input: "test-fixtures/gradle/gradle.lockfile",
expected: []pkg.Package{
{
Name: "hamcrest-core",
Version: "1.3",
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
},
{
Name: "joda-time",
Version: "2.2",
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
},
{
Name: "junit",
Version: "4.12",
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
},
},
},
}

for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
for i := range test.expected {
test.expected[i].Locations.Add(source.NewLocation(test.input))
}
pkgtest.TestFileParser(t, test.input, parseGradleLockfile, test.expected, nil)
})
}
}
1 change: 1 addition & 0 deletions syft/pkg/cataloger/java/test-fixtures/gradle/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.gradle
58 changes: 58 additions & 0 deletions syft/pkg/cataloger/java/test-fixtures/gradle/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
plugins {
id 'java'
id 'eclipse'
id 'application'
}

mainClassName = 'hello.HelloWorld'

dependencyLocking {
lockAllConfigurations()
}
// tag::repositories[]
repositories {
mavenCentral()
}
// end::repositories[]

// tag::dependencies[]
sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
implementation "joda-time:joda-time:2.2"
testImplementation "junit:junit:4.12"
}
// end::dependencies[]

// tag::jar[]
jar {
archivesBaseName = 'example-java-app-gradle'
version = '0.1.0'
manifest {
attributes(
'Main-Class': 'hello.HelloWorld'
)
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
// end::jar[]

// tag::wrapper[]
// end::wrapper[]

// to invoke: gradle resolveAndLockAll --write-locks
tasks.register('resolveAndLockAll') {
notCompatibleWithConfigurationCache("Filters configurations at execution time")
doFirst {
assert gradle.startParameter.writeDependencyLocks
}
doLast {
configurations.findAll {
// Add any custom filtering on the configurations to be resolved
it.canBeResolved
}.each { it.resolve() }
}
}
7 changes: 7 additions & 0 deletions syft/pkg/cataloger/java/test-fixtures/gradle/gradle.lockfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
joda-time:joda-time:2.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
junit:junit:4.12=testCompileClasspath,testRuntimeClasspath
org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath
empty=annotationProcessor,testAnnotationProcessor
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -uxe
# note: this can be easily done in a 1-liner, however circle CI does NOT allow volume mounts from the host in docker executors (since they are on remote hosts, where the host files are inaccessible)

PKGSDIR=$1
CTRID=$(docker create -u "$(id -u):$(id -g)" -v /example-java-app -w /example-java-app gradle:6.8.3-jdk gradle build)
CTRID=$(docker create -u "$(id -u):$(id -g)" -v /example-java-app -w /example-java-app gradle:8.0.2-jdk gradle build)

function cleanup() {
docker rm "${CTRID}"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,58 @@
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'application'
plugins {
id 'java'
id 'eclipse'
id 'application'
}

mainClassName = 'hello.HelloWorld'

dependencyLocking {
lockAllConfigurations()
}
// tag::repositories[]
repositories {
mavenCentral()
}
// end::repositories[]

// tag::jar[]
jar {
baseName = 'example-java-app-gradle'
version = '0.1.0'
}
// end::jar[]

// tag::dependencies[]
sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
compile "joda-time:joda-time:2.2"
testCompile "junit:junit:4.12"
implementation "joda-time:joda-time:2.2"
testImplementation "junit:junit:4.12"
}
// end::dependencies[]

// tag::jar[]
jar {
archivesBaseName = 'example-java-app-gradle'
version = '0.1.0'
manifest {
attributes(
'Main-Class': 'hello.HelloWorld'
)
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
// end::jar[]

// tag::wrapper[]
// end::wrapper[]
// end::wrapper[]

// to invoke: gradle resolveAndLockAll --write-locks
tasks.register('resolveAndLockAll') {
notCompatibleWithConfigurationCache("Filters configurations at execution time")
doFirst {
assert gradle.startParameter.writeDependencyLocks
}
doLast {
configurations.findAll {
// Add any custom filtering on the configurations to be resolved
it.canBeResolved
}.each { it.resolve() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
joda-time:joda-time:2.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
junit:junit:4.12=testCompileClasspath,testRuntimeClasspath
org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath
empty=annotationProcessor,testAnnotationProcessor

0 comments on commit 0fed17f

Please sign in to comment.