Skip to content

Commit

Permalink
feat: added support for gradle (#99)
Browse files Browse the repository at this point in the history
## Description

> Added support for gradle

**Related issue (if any):** fixes #issue_number_goes_here

## Checklist

- [x] I have followed this repository's contributing guidelines.
- [x] I will adhere to the project's code of conduct.

## Additional information

> Anything else?

---------

Signed-off-by: Olga Lavtar <[email protected]>
  • Loading branch information
olavtar authored Apr 4, 2024
1 parent 5518c40 commit 6563be7
Show file tree
Hide file tree
Showing 44 changed files with 26,128 additions and 166 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ jobs:
with:
python-version: '3.9'
cache: 'pip'
- name: Setup Gradle
uses: gradle/gradle-build-action@v3
- name: setup go
uses: actions/setup-go@v5
with:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ node_modules
# project stuff
http_requests
json_responses
**/.DS_Store
.idea/
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ public class ExhortExample {
<li><a href="https://www.javascript.com//">JavaScript</a> - <a href="https://www.npmjs.com//">Npm</a></li>
<li><a href="https://go.dev//">Golang</a> - <a href="https://go.dev/blog/using-go-modules//">Go Modules</a></li>
<li><a href="https://go.dev//">Python</a> - <a href="https://pypi.org/project/pip//">pip Installer</a></li>
<li><a href="https://gradle.org//">Gradle</a> - <a href="https://gradle.org/install//">Gradle Installation</a></li>

</ul>

Expand Down Expand Up @@ -274,7 +275,28 @@ Werkzeug==2.0.3
zipp==3.6.0

```
All of the 4 above examples are valid for marking a package to be ignored
<em>Gradle</em> users can add in build.gradle a comment with //exhortignore next to the package to be ignored:
```build.gradle
plugins {
id 'java'
}
group = 'groupName'
version = 'version'
repositories {
mavenCentral()
}
dependencies {
implementation "groupId:artifactId:version" // exhortignore
}
test {
useJUnitPlatform()
}
```
</ul>
All of the 5 above examples are valid for marking a package to be ignored

#### Ignore Strategies - experimental
You can specify the method to ignore dependencies in manifest (globally), by setting the environment variable `EXHORT_IGNORE_METHOD` to one of the following values: \
Expand All @@ -297,6 +319,7 @@ System.setProperty("EXHORT_SNYK_TOKEN", "my-private-snyk-token");
System.setProperty("EXHORT_MVN_PATH", "/path/to/custom/mvn");
System.setProperty("EXHORT_NPM_PATH", "/path/to/custom/npm");
System.setProperty("EXHORT_GO_PATH", "/path/to/custom/go");
System.setProperty("EXHORT_GRADLE_PATH", "/path/to/custom/gradle");
//python - python3, pip3 take precedence if python version > 3 installed
System.setProperty("EXHORT_PYTHON3_PATH", "/path/to/python3");
System.setProperty("EXHORT_PIP3_PATH", "/path/to/pip3");
Expand Down Expand Up @@ -373,6 +396,11 @@ following keys for setting custom paths for the said executables.
<td>EXHORT_GO_PATH</td>
</tr>
<tr>
<td><a href="https://gradle.org/">Gradle</a></td>
<td><em>gradle</em></td>
<td>EXHORT_GRADLE_PATH</td>
</tr>
<tr>
<td><a href="https://www.python.org/">Python programming language</a></td>
<td><em>python3</em></td>
<td>EXHORT_PYTHON3_PATH</td>
Expand Down
9 changes: 9 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@
<dependencyManagement>
<dependencies>
<!-- Dependencies -->
<dependency>
<groupId>org.tomlj</groupId>
<artifactId>tomlj</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
Expand Down Expand Up @@ -167,6 +172,10 @@

<dependencies>
<!-- Dependencies -->
<dependency>
<groupId>org.tomlj</groupId>
<artifactId>tomlj</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
Expand Down
217 changes: 217 additions & 0 deletions src/main/java/com/redhat/exhort/providers/BaseJavaProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/*
* Copyright © 2023 Red Hat, Inc.
*
* Licensed 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 com.redhat.exhort.providers;

import com.github.packageurl.MalformedPackageURLException;
import com.github.packageurl.PackageURL;
import com.redhat.exhort.Provider;
import com.redhat.exhort.sbom.Sbom;
import com.redhat.exhort.tools.Ecosystem;

import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;

public abstract class BaseJavaProvider extends Provider {

protected BaseJavaProvider(Ecosystem.Type ecosystem) {
super(ecosystem);
}

void parseDependencyTree(String src, int srcDepth, String [] lines, Sbom sbom) {
if(lines.length == 0) {
return;
}
if(lines.length == 1 && lines[0].trim().equals("")){
return;
}
int index = 0;
String target = lines[index];
int targetDepth = getDepth(target);
while(targetDepth > srcDepth && index < lines.length )
{
if(targetDepth == srcDepth + 1) {
PackageURL from = parseDep(src);
PackageURL to = parseDep(target);
if(dependencyIsNotTestScope(from) && dependencyIsNotTestScope(to)) {
sbom.addDependency(from, to);
}
}
else {
String[] modifiedLines = Arrays.copyOfRange(lines, index, lines.length);
parseDependencyTree(lines[index-1],getDepth(lines[index-1]),modifiedLines,sbom);
}
if(index< lines.length - 1) {
target = lines[++index];
targetDepth = getDepth(target);
}
else
{
index++;
}
}
}

static boolean dependencyIsNotTestScope(PackageURL artifact) {
return (Objects.nonNull(artifact.getQualifiers()) && !artifact.getQualifiers().get("scope").equals("test")) || Objects.isNull(artifact.getQualifiers());
}

PackageURL parseDep(String dep) {
//root package
DependencyAggregator dependencyAggregator = new DependencyAggregator();
// in case line in dependency tree text starts with a letter ( for root artifact).
if(dep.matches("^\\w.*"))
{
dependencyAggregator = new DependencyAggregator();
String[] parts = dep.split(":");
dependencyAggregator.groupId = parts[0];
dependencyAggregator.artifactId = parts[1];
dependencyAggregator.version = parts[3];

return dependencyAggregator.toPurl();

}
int firstDash = dep.indexOf("-");
String dependency = dep.substring(++firstDash).trim();
if(dependency.startsWith("("))
{
dependency = dependency.substring(1);
}
dependency = dependency.replace(":runtime", ":compile").replace(":provided", ":compile");
int endIndex = Math.max(dependency.indexOf(":compile"),dependency.indexOf(":test"));
int scopeLength;
if(dependency.indexOf(":compile") > -1) {
scopeLength = ":compile".length();
}
else {
scopeLength = ":test".length();
}
dependency = dependency.substring(0,endIndex + scopeLength);
String[] parts = dependency.split(":");
// contains only GAV + packaging + scope
if(parts.length == 5)
{
dependencyAggregator.groupId = parts[0];
dependencyAggregator.artifactId= parts[1];
dependencyAggregator.version = parts[3];

String conflictMessage = "omitted for conflict with";
if (dep.contains(conflictMessage))
{
dependencyAggregator.version = dep.substring(dep.indexOf(conflictMessage) + conflictMessage.length()).replace(")", "").trim();
}
}
// In case there are 6 parts, there is also a classifier for artifact (version suffix)
// contains GAV + packaging + classifier + scope
else if(parts.length == 6)
{
dependencyAggregator.groupId = parts[0];
dependencyAggregator.artifactId= parts[1];
dependencyAggregator.version = String.format("%s-%s",parts[4],parts[3]);
String conflictMessage = "omitted for conflict with";
if (dep.contains(conflictMessage))
{
dependencyAggregator.version = dep.substring(dep.indexOf(conflictMessage) + conflictMessage.length()).replace(")", "").trim();
}

}
else{
throw new RuntimeException(String.format("Cannot parse dependency into PackageUrl from line = \"%s\"",dep));
}
if(parts[parts.length - 1].matches(".*[a-z]$")) {
dependencyAggregator.scope = parts[parts.length - 1];
}
else {
int endOfLine = Integer.min(parts[parts.length - 1].indexOf(""), parts[parts.length - 1].indexOf("-"));
dependencyAggregator.scope = parts[parts.length - 1].substring(0, endOfLine).trim();
}
return dependencyAggregator.toPurl();
}

int getDepth(String line) {
if(line == null || line.trim().equals("")){
return -1;
}

if(line.matches("^\\w.*"))
{
return 0;
}

return ( (line.indexOf('-') -1 ) / 3) + 1;
}

// NOTE if we want to include "scope" tags in ignore,
// add property here and a case in the start-element-switch in the getIgnored method

/**
* Aggregator class for aggregating Dependency data over stream iterations,
**/
final static class DependencyAggregator {
String scope = "*";
String groupId;
String artifactId;
String version;
boolean ignored = false;

/**
* Get the string representation of the dependency to use as excludes
*
* @return an exclude string for the dependency:tree plugin, ie. group-id:artifact-id:*:version
*/
@Override
public String toString() {
// NOTE if you add scope, don't forget to replace the * with its value
return String.format("%s:%s:%s:%s", groupId, artifactId, scope, version);
}

boolean isValid() {
return Objects.nonNull(groupId) && Objects.nonNull(artifactId) && Objects.nonNull(version);
}

boolean isTestDependency() {
return scope.trim().equals("test");
}

PackageURL toPurl() {
try {
return new PackageURL(Ecosystem.Type.MAVEN.getType(), groupId, artifactId, version, this.scope == "*" ? null : new TreeMap<>(Map.of("scope", this.scope)), null);
} catch (MalformedPackageURLException e) {
throw new IllegalArgumentException("Unable to parse PackageURL", e);
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DependencyAggregator)) return false;
var that = (DependencyAggregator) o;
// NOTE we do not compare the ignored field
// This is required for comparing pom.xml with effective_pom.xml as the latter doesn't
// contain comments indicating ignore
return Objects.equals(this.groupId, that.groupId) &&
Objects.equals(this.artifactId, that.artifactId) &&
Objects.equals(this.version, that.version);

}

@Override
public int hashCode() {
return Objects.hash(groupId, artifactId, version);
}
}
}
Loading

0 comments on commit 6563be7

Please sign in to comment.