Skip to content

Commit

Permalink
Added NuGet support via nuget-license build plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
craigt00 committed Aug 7, 2022
1 parent ce06183 commit 3d23c78
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 5 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ Note: Please check above link for instructions or follow as mentioned below

> gradle sonarqube

### NuGet

NuGet uses the generated output from https://github.com/tomchavakis/nuget-license. Use nuget-license in your build process to create a `licenses.json` file and include it anywhere within the files scanned by SonarQube.

The new SonarQube license rules are only set against C# (not VB.NET).

## Features

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ public final class LicenseCheckRulesDefinition implements RulesDefinition
public static final String LANG_TS = "ts";
public static final String LANG_GROOVY = "grvy";
public static final String LANG_KOTLIN = "kotlin";
public static final String LANG_CS = "cs";

public static final String RULE_REPO_KEY = "licensecheck";
public static final String RULE_REPO_KEY_JS = "licensecheck-js";
public static final String RULE_REPO_KEY_TS = "licensecheck-ts";
public static final String RULE_REPO_KEY_GROOVY = "licensecheck-groovy";
public static final String RULE_REPO_KEY_KOTLIN = "licensecheck-kotlin";
public static final String RULE_REPO_KEY_CS = "licensecheck-cs";

public static final String RULE_UNLISTED_KEY = "licensecheck.unlisted";
public static final String RULE_NOT_ALLOWED_LICENSE_KEY = "licensecheck.notallowedlicense";
Expand All @@ -32,6 +34,7 @@ public void define(Context context)
context.createRepository(RULE_REPO_KEY_TS, LANG_TS),
context.createRepository(RULE_REPO_KEY_GROOVY, LANG_GROOVY),
context.createRepository(RULE_REPO_KEY_KOTLIN, LANG_KOTLIN),
context.createRepository(RULE_REPO_KEY_CS, LANG_CS)
};

for (NewRepository repo : repos)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_JS;
import static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_TS;
import static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_KOTLIN;
import static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_CS;

import java.util.Set;
import java.util.TreeSet;
Expand All @@ -23,6 +24,7 @@
import at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService;
import at.porscheinformatik.sonarqube.licensecheck.maven.MavenDependencyScanner;
import at.porscheinformatik.sonarqube.licensecheck.npm.PackageJsonDependencyScanner;
import at.porscheinformatik.sonarqube.licensecheck.nugetlicense.NugetLicenseDependencyScanner;

public class LicenseCheckSensor implements Sensor
{
Expand All @@ -38,11 +40,14 @@ public LicenseCheckSensor(Configuration configuration, ValidateLicenses validate
{
this.configuration = configuration;
this.validateLicenses = validateLicenses;
this.scanners = new Scanner[]{
this.scanners = new Scanner[]
{
new PackageJsonDependencyScanner(licenseMappingService,
configuration.getBoolean(LicenseCheckPropertyKeys.NPM_RESOLVE_TRANSITIVE_DEPS).orElse(false)),
new MavenDependencyScanner(licenseMappingService),
new GradleDependencyScanner(licenseMappingService)};
new GradleDependencyScanner(licenseMappingService),
new NugetLicenseDependencyScanner(licenseMappingService)
};
}

private static void saveDependencies(SensorContext sensorContext, Set<Dependency> dependencies)
Expand Down Expand Up @@ -117,7 +122,8 @@ public void describe(SensorDescriptor descriptor)
RULE_REPO_KEY_JS,
RULE_REPO_KEY_TS,
RULE_REPO_KEY_GROOVY,
RULE_REPO_KEY_KOTLIN);
RULE_REPO_KEY_KOTLIN,
RULE_REPO_KEY_CS);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ private static String getRepoKey(Dependency dependency)
return LicenseCheckRulesDefinition.RULE_REPO_KEY_TS;
case LicenseCheckRulesDefinition.LANG_GROOVY:
return LicenseCheckRulesDefinition.RULE_REPO_KEY_GROOVY;
case LicenseCheckRulesDefinition.LANG_CS:
return LicenseCheckRulesDefinition.RULE_REPO_KEY_CS;
case LicenseCheckRulesDefinition.LANG_JAVA:
default:
return LicenseCheckRulesDefinition.RULE_REPO_KEY;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package at.porscheinformatik.sonarqube.licensecheck.nugetlicense;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;

import org.sonar.api.batch.fs.FilePredicate;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;

import at.porscheinformatik.sonarqube.licensecheck.Dependency;
import at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition;
import at.porscheinformatik.sonarqube.licensecheck.Scanner;
import at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService;

public class NugetLicenseDependencyScanner implements Scanner
{
private static final Logger LOGGER = Loggers.get(NugetLicenseDependencyScanner.class);

private final LicenseMappingService licenseMappingService;

public NugetLicenseDependencyScanner(LicenseMappingService licenseMappingService)
{
this.licenseMappingService = licenseMappingService;
}

@Override
public Set<Dependency> scan(SensorContext context)
{
LOGGER.debug("Finding and scanning licenses.json");

FileSystem fs = context.fileSystem();
FilePredicate licenseJsonPredicate = fs.predicates().matchesPathPattern("**/licenses.json");

Set<Dependency> allDependencies = new HashSet<>();

for (InputFile licenseJsonFile : fs.inputFiles(licenseJsonPredicate))
{
context.markForPublishing(licenseJsonFile);
LOGGER.info("Scanning for licenses (file={})", licenseJsonFile.toString());
allDependencies.addAll(dependencyParser(licenseJsonFile));
}

LOGGER.debug("Nuget scanning complete.");

return allDependencies;
}

private Set<Dependency> dependencyParser(InputFile licenseJsonFile)
{
Set<Dependency> dependencies = new HashSet<>();

try (InputStream fis = licenseJsonFile.inputStream();
JsonReader jsonReader = Json.createReader(fis))
{
JsonArray licensesJson = jsonReader.readArray();

if (licensesJson != null)
{
for (int i = 0; i < licensesJson.size(); i++)
{
JsonObject nextPackage = licensesJson.getJsonObject(i);
String packageName = nextPackage.getString("PackageName");
String packageVersion = nextPackage.getString("PackageVersion");
String packageLicense = nextPackage.getString("LicenseType");

if (dependencies.stream().anyMatch(d -> packageName.equals(d.getName()) && packageVersion.equals(d.getVersion())))
{
LOGGER.debug("Package {} {} has already been encountered and will not be scanned again", packageName, packageVersion);
continue;
}

String license = licenseMappingService.mapLicense(packageLicense);

LOGGER.debug("Found license. Name: {} Version: {} License: {}", packageName, packageVersion, packageLicense);

Dependency dependency = new Dependency(packageName, packageVersion, license, LicenseCheckRulesDefinition.LANG_CS);
dependency.setInputComponent(licenseJsonFile);
dependency.setTextRange(licenseJsonFile.selectLine(1));

dependencies.add(dependency);
}
}
}
catch (IOException e)
{
LOGGER.error("Error reading license.json", e);
}

return dependencies;
}
}
2 changes: 1 addition & 1 deletion src/main/web/dashboard/dependencies.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="boxed-group boxed-group-inner">
<h3>Dependencies</h3>
<table class="data zebra">
<caption>Here you see all project dependencies from Maven (including transitive) and NPM.</caption>
<caption>Here you see all project dependencies from Maven (including transitive), NPM and NuGet.</caption>
<thead>
<tr>
<th v-for="dependency in columns" v-bind:key="dependency" v-on:click="sort(dependency)" scope="col"> {{dependency}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public void define()

new LicenseCheckRulesDefinition().define(context);

assertThat(context.repositories().size(), is(5));
assertThat(context.repositories().size(), is(6));
for (RulesDefinition.Repository repository : context.repositories())
{
assertThat(repository.rules().size(), is(2));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package at.porscheinformatik.sonarqube.licensecheck.nugetlicense;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Set;

import org.junit.Test;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultFileSystem;
import org.sonar.api.batch.sensor.SensorContext;

import at.porscheinformatik.sonarqube.licensecheck.Dependency;
import at.porscheinformatik.sonarqube.licensecheck.Scanner;
import at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService;

public class NugetLicenseDependencyScannerTest
{
private static final File RESOURCE_FOLDER = new File("src/test/resources");

private SensorContext createContext(File folder)
{
SensorContext context = mock(SensorContext.class);
InputFile packageJson = mock(InputFile.class);
when(packageJson.language()).thenReturn("json");
when(packageJson.filename()).thenReturn("licenses.json");
when(packageJson.relativePath()).thenReturn("/licenses.json");
when(packageJson.type()).thenReturn(InputFile.Type.MAIN);
try
{
when(packageJson.inputStream()).thenAnswer(i -> new FileInputStream(new File(folder, "licenses.json")));
}
catch (IOException e)
{
throw new RuntimeException(e);
}
FileSystem fileSystem = new DefaultFileSystem(folder.toPath()).add(packageJson);
when(context.fileSystem()).thenReturn(fileSystem);
return context;
}

@Test
public void testHappyPath()
{
Set<Dependency> dependencies = createScanner().scan(createContext(RESOURCE_FOLDER));

assertThat(dependencies, hasSize(4));
assertThat(dependencies, containsInAnyOrder(
new Dependency("MonoGame.Content.Builder.Task", "3.8.0.1641", "MS-PL"),
new Dependency("MonoGame.Framework.DesktopGL", "3.8.0.1641", "MS-PL"),
new Dependency("CommandLineParser", "2.8.0", "License.md"),
new Dependency("Newtonsoft.Json", "13.0.1", "MIT")));
}

@Test
public void testNoPackageJson()
{
Set<Dependency> dependencies = createScanner().scan(createContext(new File("src")));

assertThat(dependencies, hasSize(0));
}

private Scanner createScanner()
{
LicenseMappingService licenseMappingService = mock(LicenseMappingService.class);
when(licenseMappingService.mapLicense(anyString())).thenCallRealMethod();

return new NugetLicenseDependencyScanner(licenseMappingService);
}
}
1 change: 1 addition & 0 deletions src/test/resources/licenses.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"PackageName":"MonoGame.Content.Builder.Task","PackageVersion":"3.8.0.1641","PackageUrl":"https://www.monogame.net/","Copyright":"","Authors":["MonoGame Team"],"Description":"MSBuild task to automatically build content for MonoGame.","LicenseUrl":"https://licenses.nuget.org/MS-PL","LicenseType":"MS-PL","Repository":{"Type":"","Url":"https://github.com/MonoGame/MonoGame","Commit":""}},{"PackageName":"MonoGame.Framework.DesktopGL","PackageVersion":"3.8.0.1641","PackageUrl":"https://www.monogame.net/","Copyright":"","Authors":["MonoGame Team"],"Description":"The MonoGame runtime supporting Windows, Linux and macOS using SDL2 and OpenGL.","LicenseUrl":"https://licenses.nuget.org/MS-PL","LicenseType":"MS-PL","Repository":{"Type":"","Url":"https://github.com/MonoGame/MonoGame","Commit":""}},{"PackageName":"CommandLineParser","PackageVersion":"2.8.0","PackageUrl":"https://github.com/commandlineparser/commandline","Copyright":"Copyright (c) 2005 - 2020 Giacomo Stelluti Scala & Contributors","Authors":["gsscoder","nemec","ericnewton76","moh-hassan"],"Description":"Terse syntax C# command line parser for .NET. For FSharp support see CommandLineParser.FSharp. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks.","LicenseUrl":"https://www.nuget.org/packages/CommandLineParser/2.8.0/License","LicenseType":"License.md"},{"PackageName":"Newtonsoft.Json","PackageVersion":"13.0.1","PackageUrl":"https://www.newtonsoft.com/json","Copyright":"Copyright © James Newton-King 2008","Authors":["James Newton-King"],"Description":"Json.NET is a popular high-performance JSON framework for .NET","LicenseUrl":"https://licenses.nuget.org/MIT","LicenseType":"MIT"}]

0 comments on commit 3d23c78

Please sign in to comment.