Skip to content

Commit 55e61f8

Browse files
authored
Added Excluded license types (#180)
1 parent 0ef4280 commit 55e61f8

12 files changed

+182
-46
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ Usage: dotnet-project-licenses [options]
3333
<tr><td> -m, --md </td><td>(Default: false) Save licenses list in a markdown file (licenses.md)</td></tr>
3434
<tr><td> --include-project-file </td><td>(Default: false) Add project file path to information when enabled</td></tr>
3535
<tr><td> -l, --log-level </td><td>(Default: Error) Set log level for output display. Options: Error,Warning,Information,Verbose</td></tr>
36-
<tr><td> --allowed-license-types </td><td>Simple json file of a text array of allowable licenses, if no file is given, all are assumed allowed</td></tr>
36+
<tr><td> --allowed-license-types </td><td>Simple json file of a text array of allowable licenses, if no file is given, all are assumed allowed. Cannot be used alongside 'forbidden-license-types'.</td></tr>
37+
<tr><td> --forbidden-license-types </td><td>Simple json file of a text array of forbidden licenses, if no file is given, none are assumed forbidden. Cannot be used alongside 'allowed-license-types'.</td></tr>
3738
<tr><td> --manual-package-information</td><td>Simple json file of an array of LibraryInfo objects for manually determined packages</td></tr>
3839
<tr><td> --licenseurl-to-license-mappings</td><td>Simple json file of Dictionary<string,string> to override default mappings</td></tr>
3940
<tr><td> -t, --include-transitive </td><td>Include distinct transitive package licenses per project file</td></tr>

src/InvalidLicensesException.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,21 @@ namespace NugetUtility
55
{
66
public class InvalidLicensesException<T> : Exception
77
{
8-
public InvalidLicensesException(IValidationResult<T> validationResult, ICollection<string> allowedLicenses)
9-
: base(GetMessage(validationResult, allowedLicenses))
8+
public InvalidLicensesException(ValidationResult<T> validationResult, PackageOptions options)
9+
: base(GetMessage(validationResult, options))
1010
{
1111
}
1212

13-
private static string GetMessage(IValidationResult<T> validationResult, ICollection<string> allowedLicenses)
13+
private static string GetMessage(
14+
IValidationResult<T> validationResult,
15+
PackageOptions options)
1416
{
15-
allowedLicenses ??= Array.Empty<string>();
16-
var message = $"Only the following licenses are allowed: {string.Join(", ", allowedLicenses.ToArray())}{Environment.NewLine}";
17+
var allowedLicenses = options?.AllowedLicenseType ?? Array.Empty<string>();
18+
var forbiddenLicenses = options?.ForbiddenLicenseType ?? Array.Empty<string>();
19+
20+
var message = allowedLicenses.Any()
21+
? $"Only the following licenses are allowed: {string.Join(", ", allowedLicenses.ToArray())}{Environment.NewLine}"
22+
: $"The following licenses are forbidden: {string.Join(", ", forbiddenLicenses.ToArray())}{Environment.NewLine}";
1723

1824
if (validationResult is IValidationResult<KeyValuePair<string, Package>> packageValidation)
1925
{

src/Methods.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ private LibraryInfo MapPackageToLibraryInfo(Package item, string projectFile)
510510
}
511511
};
512512
}
513-
513+
514514
public IValidationResult<KeyValuePair<string, Package>> ValidateLicenses(Dictionary<string, PackageList> projectPackages)
515515
{
516516
if (_packageOptions.AllowedLicenseType.Count == 0)
@@ -519,6 +519,7 @@ public IValidationResult<KeyValuePair<string, Package>> ValidateLicenses(Diction
519519
}
520520

521521
WriteOutput(() => $"Starting {nameof(ValidateLicenses)}...", logLevel: LogLevel.Verbose);
522+
522523
var invalidPackages = projectPackages
523524
.SelectMany(kvp => kvp.Value.Select(p => new KeyValuePair<string, Package>(kvp.Key, p.Value)))
524525
.Where(p => !_packageOptions.AllowedLicenseType.Any(allowed =>
@@ -558,13 +559,21 @@ public IValidationResult<KeyValuePair<string, Package>> ValidateLicenses(Diction
558559
}
559560

560561
public ValidationResult<LibraryInfo> ValidateLicenses(List<LibraryInfo> projectPackages)
562+
{
563+
return _packageOptions.AllowedLicenseType.Any()
564+
? ValidateAllowedLicenses(projectPackages)
565+
: ValidateForbiddenLicenses(projectPackages);
566+
}
567+
568+
private ValidationResult<LibraryInfo> ValidateAllowedLicenses(List<LibraryInfo> projectPackages)
561569
{
562570
if (_packageOptions.AllowedLicenseType.Count == 0)
563571
{
564572
return new ValidationResult<LibraryInfo> { IsValid = true };
565573
}
566574

567-
WriteOutput(() => $"Starting {nameof(ValidateLicenses)}...", logLevel: LogLevel.Verbose);
575+
WriteOutput(() => $"Starting {nameof(ValidateAllowedLicenses)}...", logLevel: LogLevel.Verbose);
576+
568577
var invalidPackages = projectPackages
569578
.Where(p => !_packageOptions.AllowedLicenseType.Any(allowed =>
570579
{
@@ -588,6 +597,27 @@ public ValidationResult<LibraryInfo> ValidateLicenses(List<LibraryInfo> projectP
588597
return new ValidationResult<LibraryInfo> { IsValid = invalidPackages.Count == 0, InvalidPackages = invalidPackages };
589598
}
590599

600+
private ValidationResult<LibraryInfo> ValidateForbiddenLicenses(List<LibraryInfo> projectPackages)
601+
{
602+
if (_packageOptions.ForbiddenLicenseType.Count == 0)
603+
{
604+
return new ValidationResult<LibraryInfo> { IsValid = true };
605+
}
606+
607+
WriteOutput(() => $"Starting {nameof(ValidateForbiddenLicenses)}...", logLevel: LogLevel.Verbose);
608+
609+
var invalidPackages = projectPackages
610+
.Where(LicenseIsForbidden)
611+
.ToList();
612+
613+
return new ValidationResult<LibraryInfo> { IsValid = invalidPackages.Count == 0, InvalidPackages = invalidPackages };
614+
615+
bool LicenseIsForbidden(LibraryInfo info)
616+
{
617+
return _packageOptions.ForbiddenLicenseType.Contains(info.LicenseType);
618+
}
619+
}
620+
591621
private async Task<T> GetNuGetPackageFileResult<T>(string packageName, string versionNumber, string fileInPackage)
592622
where T : class
593623
{

src/NugetUtility.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@
1212
<RepositoryType>git</RepositoryType>
1313
<PackageId>dotnet-project-licenses</PackageId>
1414
<ToolCommandName>dotnet-project-licenses</ToolCommandName>
15-
<Version>2.5.0</Version>
15+
<Version>2.6.0</Version>
1616
<Authors>Tom Chavakis, Lexy2, senslen</Authors>
1717
<Company>-</Company>
1818
<Title>.NET Core Tool to print a list of the licenses of a projects</Title>
1919
<PackageProjectUrl>https://github.com/tomchavakis/nuget-license</PackageProjectUrl>
2020
<GeneratePackageOnBuild Condition="'$(GeneratePackageOnBuild)' == ''">false</GeneratePackageOnBuild>
21+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
2122
</PropertyGroup>
22-
23+
2324
<ItemGroup>
2425
<PackageReference Include="CommandLineParser" Version="2.9.1" />
2526
<PackageReference Include="HtmlAgilityPack" Version="1.11.45" />

src/PackageOptions.cs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@ public class PackageOptions
1414
private readonly Regex UserRegexRegex = new Regex("^([/#])(.+)\\1$");
1515

1616
private ICollection<string> _allowedLicenseTypes = new Collection<string>();
17+
private ICollection<string> _forbiddenLicenseTypes = new Collection<string>();
1718
private ICollection<LibraryInfo> _manualInformation = new Collection<LibraryInfo>();
1819
private ICollection<string> _projectFilter = new Collection<string>();
1920
private ICollection<string> _packagesFilter = new Collection<string>();
20-
private Dictionary<string, string> _customLicenseToUrlMappings = new Dictionary<string, string>();
21+
private Dictionary<string, string> _customLicenseToUrlMappings = new();
2122

22-
[Option("allowed-license-types", Default = null, HelpText = "Simple json file of a text array of allowable licenses, if no file is given, all are assumed allowed")]
23+
[Option("allowed-license-types", Default = null, HelpText = "Simple json file of a text array of allowable licenses, if no file is given, all are assumed allowed. Cannot be used alongside 'forbidden-license-types'.")]
2324
public string AllowedLicenseTypesOption { get; set; }
25+
26+
[Option("forbidden-license-types", Default = null, HelpText = "Simple json file of a text array of forbidden licenses, if no file is given, none are assumed forbidden. Cannot be used alongside 'allowed-license-types'.")]
27+
public string ForbiddenLicenseTypesOption { get; set; }
2428

2529
[Option("include-project-file", Default = false, HelpText = "Adds project file path to information when enabled.")]
2630
public bool IncludeProjectFile { get; set; }
@@ -94,20 +98,20 @@ public static IEnumerable<Example> Examples
9498
get
9599
{
96100
return new List<Example>() {
97-
new Example ("Simple", new PackageOptions { ProjectDirectory = "~/Projects/test-project" }),
98-
new Example ("VS Solution", new PackageOptions { ProjectDirectory = "~/Projects/test-project/project.sln" }),
99-
new Example ("Unique VS Solution to Custom JSON File", new PackageOptions {
101+
new Example ("Simple", new PackageOptions { ProjectDirectory = "~/Projects/test-project" }),
102+
new Example ("VS Solution", new PackageOptions { ProjectDirectory = "~/Projects/test-project/project.sln" }),
103+
new Example ("Unique VS Solution to Custom JSON File", new PackageOptions {
100104
ProjectDirectory = "~/Projects/test-project/project.sln",
101105
UniqueOnly = true,
102106
JsonOutput = true,
103107
OutputFileName = @"~/Projects/another-folder/licenses.json"
104-
}),
105-
new Example("Export all license texts in a specific directory with verbose log", new PackageOptions
106-
{
107-
LogLevelThreshold = LogLevel.Verbose,
108-
OutputDirectory = "~/Projects/exports",
109-
ExportLicenseTexts = true,
110-
}),
108+
}),
109+
new Example("Export all license texts in a specific directory with verbose log", new PackageOptions
110+
{
111+
LogLevelThreshold = LogLevel.Verbose,
112+
OutputDirectory = "~/Projects/exports",
113+
ExportLicenseTexts = true,
114+
}),
111115
};
112116
}
113117
}
@@ -122,6 +126,16 @@ public ICollection<string> AllowedLicenseType
122126
}
123127
}
124128

129+
public ICollection<string> ForbiddenLicenseType
130+
{
131+
get
132+
{
133+
if (_forbiddenLicenseTypes.Any()) { return _forbiddenLicenseTypes; }
134+
135+
return _forbiddenLicenseTypes = ReadListFromFile<string>(ForbiddenLicenseTypesOption);
136+
}
137+
}
138+
125139
public ICollection<LibraryInfo> ManualInformation
126140
{
127141
get

src/Program.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using CommandLine;
22
using System;
33
using System.Collections.Generic;
4+
using System.Linq;
45
using System.Threading.Tasks;
56

67
namespace NugetUtility
@@ -33,6 +34,14 @@ private static async Task<int> Execute(PackageOptions options)
3334
return 1;
3435
}
3536

37+
if (options.ForbiddenLicenseType.Any() && options.AllowedLicenseType.Any())
38+
{
39+
Console.WriteLine("ERROR(S):");
40+
Console.WriteLine("--allowed-license-types\tCannot be used with the --forbidden-license-types option.");
41+
42+
return 1;
43+
}
44+
3645
if (options.UseProjectAssetsJson && !options.IncludeTransitive)
3746
{
3847
Console.WriteLine("ERROR(S):");
@@ -51,10 +60,11 @@ private static async Task<int> Execute(PackageOptions options)
5160

5261
try
5362
{
54-
Methods methods = new Methods(options);
63+
var methods = new Methods(options);
5564
var projectsWithPackages = await methods.GetPackages();
5665
var mappedLibraryInfo = methods.MapPackagesToLibraryInfo(projectsWithPackages);
57-
HandleInvalidLicenses(methods, mappedLibraryInfo, options.AllowedLicenseType);
66+
67+
HandleInvalidLicenses(methods, mappedLibraryInfo, options);
5868

5969
if (options.ExportLicenseTexts)
6070
{
@@ -92,15 +102,13 @@ private static async Task<int> Execute(PackageOptions options)
92102
}
93103
}
94104

95-
96-
97-
private static void HandleInvalidLicenses(Methods methods, List<LibraryInfo> libraries, ICollection<string> allowedLicenseType)
105+
private static void HandleInvalidLicenses(Methods methods, List<LibraryInfo> libraries, PackageOptions options)
98106
{
99107
var invalidPackages = methods.ValidateLicenses(libraries);
100108

101109
if (!invalidPackages.IsValid)
102110
{
103-
throw new InvalidLicensesException<LibraryInfo>(invalidPackages, allowedLicenseType);
111+
throw new InvalidLicensesException<LibraryInfo>(invalidPackages, options);
104112
}
105113
}
106114
}

src/Validators/LicenseValidator.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace NugetUtility.Validators;
2+
3+
public class LicenseValidator
4+
{
5+
6+
}

tests/NugetUtility.Tests/InvalidLicenseTests.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@ public class InvalidLicenseTests
1515
[Test]
1616
public void Should_Format_Exceptions_On_NewLines_With_Allowed_Header(bool hasAllowed)
1717
{
18-
var result = new ValidationResult<KeyValuePair<string,Package>>
18+
var result = new ValidationResult<KeyValuePair<string, Package>>
1919
{
2020
IsValid = false,
2121
InvalidPackages = new List<KeyValuePair<string, Package>>
2222
{
23-
new KeyValuePair<string, Package>(@"c:\some\project.csproj",new Package{ Metadata = new Metadata { Id = "BadLicense", Version = "0.1.0"} }),
24-
new KeyValuePair<string, Package>(@"c:\some\project.csproj",new Package{ Metadata = new Metadata { Id = "BadLicense2", Version = "0.1.0"} }),
25-
new KeyValuePair<string, Package>(@"c:\some\project.csproj",new Package{ Metadata = new Metadata { Id = "BadLicense3", Version = "0.1.0"} }),
23+
new(@"c:\some\project.csproj", new Package {Metadata = new Metadata {Id = "BadLicense", Version = "0.1.0"}}),
24+
new(@"c:\some\project.csproj", new Package {Metadata = new Metadata {Id = "BadLicense2", Version = "0.1.0"}}),
25+
new(@"c:\some\project.csproj", new Package {Metadata = new Metadata {Id = "BadLicense3", Version = "0.1.0"}}),
2626
}
2727
};
28-
var exception = new InvalidLicensesException<KeyValuePair<string,Package>>(result, !hasAllowed ? null : new List<string> { "MIT" });
28+
29+
var options = new PackageOptions
30+
{
31+
AllowedLicenseTypesOption = @"../../../SampleAllowedLicenses.json"
32+
};
33+
34+
var exception = new InvalidLicensesException<KeyValuePair<string, Package>>(result, !hasAllowed ? null : options);
2935

3036
exception.Should().NotBeNull();
3137
exception.Message.Split(Environment.NewLine)

tests/NugetUtility.Tests/MethodsTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,53 @@ public async Task GetPackages_InputJson_Should_OnlyParseGivenProjects()
261261
validationResult.InvalidPackages.Count.Should().Be(5);
262262
}
263263

264+
[Test]
265+
public async Task ValidateLicenses_ForbiddenLicenses_Should_Be_Invalid()
266+
{
267+
var methods = new Methods(new PackageOptions
268+
{
269+
ProjectsFilterOption = @"../../../SampleProjectFilters.json",
270+
ForbiddenLicenseTypesOption = @"../../../SampleForbiddenLicenses.json",
271+
ProjectDirectory = TestSetup.ThisProjectSolutionPath,
272+
Timeout = 10
273+
});
274+
275+
var result = await methods.GetPackages();
276+
var mapped = methods.MapPackagesToLibraryInfo(result);
277+
var validationResult = methods.ValidateLicenses(mapped);
278+
279+
validationResult.IsValid.Should().BeFalse();
280+
validationResult.InvalidPackages.Should().HaveCount(2);
281+
validationResult.InvalidPackages.ElementAt(0).LicenseType.Should().Be("MIT");
282+
validationResult.InvalidPackages.ElementAt(0).PackageName.Should().Be("HtmlAgilityPack");
283+
validationResult.InvalidPackages.ElementAt(1).LicenseType.Should().Be("MIT");
284+
validationResult.InvalidPackages.ElementAt(1).PackageName.Should().Be("Newtonsoft.Json");
285+
}
286+
287+
[Test]
288+
public async Task ValidateLicenses_AllowedLicenses_Should_Be_Invalid()
289+
{
290+
var methods = new Methods(new PackageOptions
291+
{
292+
ProjectsFilterOption = @"../../../SampleProjectFilters.json",
293+
AllowedLicenseTypesOption = @"../../../SampleAllowedLicenses.json",
294+
ProjectDirectory = TestSetup.ThisProjectSolutionPath,
295+
Timeout = 10
296+
});
297+
298+
var result = await methods.GetPackages();
299+
var mapped = methods.MapPackagesToLibraryInfo(result);
300+
var validationResult = methods.ValidateLicenses(mapped);
301+
302+
validationResult.IsValid.Should().BeFalse();
303+
validationResult.InvalidPackages.Should().HaveCount(5);
304+
validationResult.InvalidPackages.ElementAt(0).LicenseType.Should().Be("License.md");
305+
validationResult.InvalidPackages.ElementAt(1).LicenseType.Should().Be("MIT");
306+
validationResult.InvalidPackages.ElementAt(2).LicenseType.Should().Be("MIT");
307+
validationResult.InvalidPackages.ElementAt(3).LicenseType.Should().Be("Apache-2.0");
308+
validationResult.InvalidPackages.ElementAt(4).LicenseType.Should().Be("MS-EULA");
309+
}
310+
264311
[Test]
265312
public async Task GetProjectReferencesFromAssetsFile_Should_Resolve_Transitive_Assets()
266313
{

0 commit comments

Comments
 (0)