diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 94c7c1e..ad00392 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -8,14 +8,16 @@ jobs:
name: Build, pack and publish
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-dotnet@v1
+ - name: Checkout code
+ uses: actions/checkout@v3
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
with:
- dotnet-version: '6.0.x'
+ dotnet-version: '8.0.x'
- name: Publish on version change
id: publish_nuget
- uses: alirezanet/publish-nuget@v3.0.4
+ uses: alirezanet/publish-nuget@v3.1.0
with:
# Filepath of the project to be packaged, relative to root of repository
PROJECT_FILE_PATH: src/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj
diff --git a/src/DotnetThirdPartyNotices/Directory.Packages.props b/src/DotnetThirdPartyNotices/Directory.Packages.props
new file mode 100644
index 0000000..28afc6b
--- /dev/null
+++ b/src/DotnetThirdPartyNotices/Directory.Packages.props
@@ -0,0 +1,30 @@
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj b/src/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj
index a73858e..28be4c2 100644
--- a/src/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj
+++ b/src/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj
@@ -1,14 +1,14 @@
-
+
Exe
- net6.0
+ net8.0
default
true
dotnet-thirdpartynotices
DotnetThirdPartyNotices
A .NET tool to generate file with third party legal notices
- 0.2.7
+ 0.2.8
MIT
git
https://github.com/bugproof/DotnetThirdPartyNotices
@@ -22,14 +22,14 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/src/DotnetThirdPartyNotices/Extensions/ProjectExtensions.cs b/src/DotnetThirdPartyNotices/Extensions/ProjectExtensions.cs
index 39b795f..6a9e051 100644
--- a/src/DotnetThirdPartyNotices/Extensions/ProjectExtensions.cs
+++ b/src/DotnetThirdPartyNotices/Extensions/ProjectExtensions.cs
@@ -61,13 +61,7 @@ private static IEnumerable ResolveFilesUsingResolveAssemblyRef
if (item.GetMetadataValue("ResolvedFrom") == "{HintPathFromItem}" && item.GetMetadataValue("HintPath").StartsWith("..\\packages"))
{
- var packagePath = Utils.GetPackagePathFromAssemblyPath(assemblyPath);
- if (packagePath == null)
- throw new ApplicationException($"Cannot find package path from assembly path ({assemblyPath})");
-
- var nuPkgFileName = Directory.GetFiles(packagePath, "*.nupkg", SearchOption.TopDirectoryOnly).Single();
-
- var nuSpec = NuSpec.FromNupkg(nuPkgFileName);
+ var nuSpec = NuSpec.FromAssemble(assemblyPath) ?? throw new ApplicationException( $"Cannot find package path from assembly path ({assemblyPath})" );
resolvedFileInfo.NuSpec = nuSpec;
resolvedFileInfos.Add(resolvedFileInfo);
}
@@ -97,14 +91,7 @@ private static IEnumerable ResolveFilesUsingComputeFilesToPubl
// Skip if it's not a NuGet package
continue;
}
-
- var packagePath = Utils.GetPackagePathFromAssemblyPath(assemblyPath);
- if (packagePath == null)
- throw new ApplicationException($"Cannot find package path from assembly path ({assemblyPath})");
-
- // TODO: don't think this is reliable because I'm not sure if .nuspec will always be there, or if it will always be named tha way
- var nuSpecFilePath = Path.Combine(packagePath, $"{packageName}.nuspec"); // Directory.GetFiles(packageFolder, "*.nuspec", SearchOption.TopDirectoryOnly).SingleOrDefault();
- var nuSpec = NuSpec.FromFile(nuSpecFilePath);
+ var nuSpec = NuSpec.FromAssemble( assemblyPath ) ?? throw new ApplicationException( $"Cannot find package path from assembly path ({assemblyPath})" ); ;
var relativePath = item.GetMetadataValue("RelativePath");
var resolvedFileInfo = new ResolvedFileInfo
diff --git a/src/DotnetThirdPartyNotices/Extensions/ResolvedFileInfoExtensions.cs b/src/DotnetThirdPartyNotices/Extensions/ResolvedFileInfoExtensions.cs
index e1ad53f..998f7d4 100644
--- a/src/DotnetThirdPartyNotices/Extensions/ResolvedFileInfoExtensions.cs
+++ b/src/DotnetThirdPartyNotices/Extensions/ResolvedFileInfoExtensions.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
@@ -62,15 +63,16 @@ public static async Task ResolveLicense(this ResolvedFileInfo resolvedFi
if (resolvedFileInfo == null) throw new ArgumentNullException(nameof(resolvedFileInfo));
string license = null;
if (resolvedFileInfo.NuSpec != null)
- license = await ResolveLicense(resolvedFileInfo.NuSpec);
+ license = await ResolveLicenseFromNuspec(resolvedFileInfo);
- return license ?? await ResolveLicenseFromFileVersionInfo(resolvedFileInfo.VersionInfo);
+ return license ?? await ResolveLicense(resolvedFileInfo.VersionInfo);
}
private static readonly Dictionary LicenseCache = new();
- private static async Task ResolveLicense(NuSpec nuSpec)
+ private static async Task ResolveLicenseFromNuspec( ResolvedFileInfo resolvedFileInfo )
{
+ var nuSpec = resolvedFileInfo.NuSpec;
if (LicenseCache.ContainsKey(nuSpec.Id))
return LicenseCache[nuSpec.Id];
@@ -78,11 +80,24 @@ private static async Task ResolveLicense(NuSpec nuSpec)
var repositoryUrl = nuSpec.RepositoryUrl;
var projectUrl = nuSpec.ProjectUrl;
+ if (!string.IsNullOrEmpty(nuSpec.LicenseRelativePath))
+ {
+ if (LicenseCache.TryGetValue(nuSpec.LicenseRelativePath, out string value))
+ return value;
+ var license3 = await ResolveLicenseFromRelativePath(resolvedFileInfo.VersionInfo, nuSpec.LicenseRelativePath);
+ if (license3 != null)
+ {
+ LicenseCache[nuSpec.Id] = license3;
+ LicenseCache[nuSpec.LicenseRelativePath] = license3;
+ return license3;
+ }
+ }
+
// Try to get the license from license url
- if (!string.IsNullOrEmpty(licenseUrl))
+ if (!string.IsNullOrEmpty(nuSpec.LicenseUrl))
{
- if (LicenseCache.ContainsKey(licenseUrl))
- return LicenseCache[licenseUrl];
+ if (LicenseCache.TryGetValue(licenseUrl, out string value))
+ return value;
var license = await ResolveLicenseFromLicenseUri(new Uri(nuSpec.LicenseUrl));
if (license != null)
@@ -96,8 +111,8 @@ private static async Task ResolveLicense(NuSpec nuSpec)
// Try to get the license from repository url
if (!string.IsNullOrEmpty(repositoryUrl))
{
- if (LicenseCache.ContainsKey(repositoryUrl ))
- return LicenseCache[repositoryUrl];
+ if (LicenseCache.TryGetValue(repositoryUrl, out string value))
+ return value;
var license = await ResolveLicenseFromRepositoryUri(new Uri(repositoryUrl));
if (license != null)
{
@@ -108,17 +123,32 @@ private static async Task ResolveLicense(NuSpec nuSpec)
}
// Otherwise try to get the license from project url
- if (string.IsNullOrEmpty(projectUrl)) return null;
+ if (string.IsNullOrEmpty(projectUrl))
+ {
+ if (LicenseCache.TryGetValue(projectUrl, out string value))
+ return value;
- if (LicenseCache.ContainsKey(projectUrl))
- return LicenseCache[projectUrl];
+ var license2 = await ResolveLicenseFromProjectUri(new Uri(projectUrl));
+ if (license2 != null)
+ {
+ LicenseCache[nuSpec.Id] = license2;
+ LicenseCache[nuSpec.ProjectUrl] = license2;
+ return license2;
+ }
+ }
- var license2 = await ResolveLicenseFromProjectUri(new Uri(projectUrl));
- if (license2 == null) return null;
+ return null;
+ }
- LicenseCache[nuSpec.Id] = license2;
- LicenseCache[nuSpec.ProjectUrl] = license2;
- return license2;
+ private static async Task ResolveLicense(FileVersionInfo fileVersionInfo)
+ {
+ if (LicenseCache.ContainsKey(fileVersionInfo.FileName))
+ return LicenseCache[fileVersionInfo.FileName];
+ var license = await ResolveLicenseFromFileVersionInfo(fileVersionInfo);
+ if(license == null)
+ return null;
+ LicenseCache[fileVersionInfo.FileName] = license;
+ return license;
}
private static async Task ResolveLicenseFromLicenseUri(Uri licenseUri)
@@ -149,6 +179,15 @@ private static async Task ResolveLicenseFromRepositoryUri(Uri repository
return await repositoryUri.GetPlainText();
}
+ private static async Task ResolveLicenseFromRelativePath(FileVersionInfo fileVersionInfo, string relativePath)
+ {
+ var packagePath = Utils.GetPackagePath( fileVersionInfo.FileName );
+ var licenseFullPath = Path.Combine( packagePath, relativePath );
+ if (!licenseFullPath.EndsWith(".txt") && !licenseFullPath.EndsWith( ".md" ) || !File.Exists( licenseFullPath ))
+ return null;
+ return await File.ReadAllTextAsync( licenseFullPath );
+ }
+
private static async Task ResolveLicenseFromProjectUri(Uri projectUri)
{
if (TryFindProjectUriLicenseResolver(projectUri, out var projectUriLicenseResolver))
diff --git a/src/DotnetThirdPartyNotices/GithubService.cs b/src/DotnetThirdPartyNotices/GithubService.cs
index 80b7a1a..6f82241 100644
--- a/src/DotnetThirdPartyNotices/GithubService.cs
+++ b/src/DotnetThirdPartyNotices/GithubService.cs
@@ -34,7 +34,10 @@ public async Task GetLicenseContentFromRepositoryPath(string repositoryP
repositoryPath = repositoryPath.TrimEnd('/');
if (repositoryPath.EndsWith(".git"))
repositoryPath = repositoryPath[..^4];
- var json = await _httpClient.GetStringAsync($"repos{repositoryPath}/license");
+ var response = await _httpClient.GetAsync($"repos{repositoryPath}/license");
+ if (!response.IsSuccessStatusCode)
+ return null;
+ var json = await response.Content.ReadAsStringAsync();
var jsonDocument = JsonDocument.Parse(json);
var rootElement = jsonDocument.RootElement;
diff --git a/src/DotnetThirdPartyNotices/LicenseResolvers/LocalPackageLicenseResolver.cs b/src/DotnetThirdPartyNotices/LicenseResolvers/LocalPackageLicenseResolver.cs
index cca802c..eaf96f3 100644
--- a/src/DotnetThirdPartyNotices/LicenseResolvers/LocalPackageLicenseResolver.cs
+++ b/src/DotnetThirdPartyNotices/LicenseResolvers/LocalPackageLicenseResolver.cs
@@ -11,22 +11,23 @@ namespace DotnetThirdPartyNotices.LicenseResolvers;
internal class LocalPackageLicenseResolver : IFileVersionInfoLicenseResolver
{
- public bool CanResolve( FileVersionInfo fileVersionInfo ) => true;
+ public bool CanResolve( FileVersionInfo fileVersionInfo ) => GetLicensePath(fileVersionInfo) != null;
- public async Task Resolve( FileVersionInfo fileVersionInfo )
+ public async Task Resolve(FileVersionInfo fileVersionInfo)
{
- var packageName = Path.GetFileNameWithoutExtension(fileVersionInfo.FileName);
- var directoryParts = Path.GetDirectoryName(fileVersionInfo.FileName ).Split('\\', StringSplitOptions.RemoveEmptyEntries);
- for ( var i = 0; i < directoryParts.Length; i++ )
+ var licensePath = GetLicensePath(fileVersionInfo);
+ if (licensePath == null)
+ return null;
+ return await File.ReadAllTextAsync( licensePath );
+ }
+
+ private string GetLicensePath( FileVersionInfo fileVersionInfo )
+ {
+ var directoryPath = Utils.GetPackagePath( fileVersionInfo.FileName ) ?? Path.GetDirectoryName( fileVersionInfo.FileName );
+ return Directory.EnumerateFiles( directoryPath, "license.*", new EnumerationOptions
{
- var directoryPath = string.Join('\\', directoryParts.SkipLast(i));
- var licensePath = Directory.EnumerateFiles(directoryPath, "license.txt", SearchOption.TopDirectoryOnly)
- .FirstOrDefault();
- if (licensePath != null)
- return await File.ReadAllTextAsync(licensePath);
- if (directoryPath.EndsWith($"\\{packageName}", StringComparison.OrdinalIgnoreCase))
- break;
- }
- return null;
+ MatchCasing = MatchCasing.CaseInsensitive,
+ RecurseSubdirectories = false
+ } ).FirstOrDefault( x => x.EndsWith( "\\license.txt", StringComparison.OrdinalIgnoreCase ) || x.EndsWith( "\\license.md", StringComparison.OrdinalIgnoreCase ) );
}
}
diff --git a/src/DotnetThirdPartyNotices/NuGetVersion.cs b/src/DotnetThirdPartyNotices/NuGetVersion.cs
deleted file mode 100644
index 36b4ab9..0000000
--- a/src/DotnetThirdPartyNotices/NuGetVersion.cs
+++ /dev/null
@@ -1,122 +0,0 @@
-using System;
-using System.Linq;
-
-namespace DotnetThirdPartyNotices;
-
-// based on https://github.com/NuGetArchive/NuGet.Versioning/blob/0f25e04c3a33d2dff11cbb97e1c0827cf5bf6da6/src/NuGet.Versioning/NuGetVersionFactory.cs
-internal static class NuGetVersion
-{
- public static bool IsValid(string value)
- {
- if (value == null) return false;
-
- // trim the value before passing it in since we not strict here
- var sections = ParseSections(value.Trim());
-
- // null indicates the string did not meet the rules
- if (sections == null || string.IsNullOrEmpty(sections.Item1)) return false;
- var versionPart = sections.Item1;
-
- if (versionPart.IndexOf('.') < 0)
- {
- // System.Version requires at least a 2 part version to parse.
- versionPart += ".0";
- }
-
- if (!Version.TryParse(versionPart, out _)) return false;
- // labels
- if (sections.Item2 != null && !sections.Item2.All(s => IsValidPart(s, false)))
- {
- return false;
- }
-
- return sections.Item3 == null || IsValid(sections.Item3, true);
- }
-
- internal static bool IsLetterOrDigitOrDash(char c)
- {
- int x = c;
-
- // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"
- return (x >= 48 && x <= 57) || (x >= 65 && x <= 90) || (x >= 97 && x <= 122) || x == 45;
- }
-
- internal static bool IsValid(string s, bool allowLeadingZeros)
- {
- return s.Split('.').All(p => IsValidPart(p, allowLeadingZeros));
- }
-
- internal static bool IsValidPart(string s, bool allowLeadingZeros)
- {
- return IsValidPart(s.ToCharArray(), allowLeadingZeros);
- }
-
- internal static bool IsValidPart(char[] chars, bool allowLeadingZeros)
- {
- var result = chars.Length != 0;
-
- // 0 is fine, but 00 is not.
- // 0A counts as an alpha numeric string where zeros are not counted
- if (!allowLeadingZeros && chars.Length > 1 && chars[0] == '0' && chars.All(char.IsDigit))
- {
- // no leading zeros in labels allowed
- result = false;
- }
- else
- {
- result &= chars.All(IsLetterOrDigitOrDash);
- }
-
- return result;
- }
-
- internal static Tuple ParseSections(string value)
- {
- string versionString = null;
- string[] releaseLabels = null;
- string buildMetadata = null;
-
- var dashPos = -1;
- var plusPos = -1;
-
- var chars = value.ToCharArray();
-
- for (var i = 0; i < chars.Length; i++)
- {
- var end = (i == chars.Length - 1);
-
- if (dashPos < 0)
- {
- if (!end && chars[i] != '-' && chars[i] != '+') continue;
- var endPos = i + (end ? 1 : 0);
- versionString = value.Substring(0, endPos);
-
- dashPos = i;
-
- if (chars[i] == '+')
- {
- plusPos = i;
- }
- }
- else if (plusPos < 0)
- {
- if (!end && chars[i] != '+') continue;
- var start = dashPos + 1;
- var endPos = i + (end ? 1 : 0);
- var releaseLabel = value.Substring(start, endPos - start);
-
- releaseLabels = releaseLabel.Split('.');
-
- plusPos = i;
- }
- else if (end)
- {
- var start = plusPos + 1;
- var endPos = i + (end ? 1 : 0);
- buildMetadata = value.Substring(start, endPos - start);
- }
- }
-
- return new Tuple(versionString, releaseLabels, buildMetadata);
- }
-}
\ No newline at end of file
diff --git a/src/DotnetThirdPartyNotices/NuSpec.cs b/src/DotnetThirdPartyNotices/NuSpec.cs
index 93dfe4d..9d515e4 100644
--- a/src/DotnetThirdPartyNotices/NuSpec.cs
+++ b/src/DotnetThirdPartyNotices/NuSpec.cs
@@ -14,6 +14,7 @@ public record NuSpec
public string LicenseUrl { get; init; }
public string ProjectUrl { get; init; }
public string RepositoryUrl { get; init; }
+ public string LicenseRelativePath { get; init; }
private static NuSpec FromTextReader(TextReader streamReader)
{
@@ -31,20 +32,21 @@ private static NuSpec FromTextReader(TextReader streamReader)
Version = metadata.Element(ns + "version")?.Value,
LicenseUrl = metadata.Element(ns + "licenseUrl")?.Value,
ProjectUrl = metadata.Element(ns + "projectUrl")?.Value,
- RepositoryUrl = metadata.Element(ns + "repository")?.Attribute("url")?.Value
+ RepositoryUrl = metadata.Element(ns + "repository")?.Attribute("url")?.Value,
+ LicenseRelativePath = metadata.Elements(ns + "license").Where(x => x.Attribute("type")?.Value == "file").FirstOrDefault()?.Value
};
}
public static NuSpec FromFile(string fileName)
{
- if (fileName == null) throw new ArgumentNullException(nameof(fileName));
+ ArgumentNullException.ThrowIfNull( fileName );
using var xmlReader = new StreamReader(fileName);
return FromTextReader(xmlReader);
}
public static NuSpec FromNupkg(string fileName)
{
- if (fileName == null) throw new ArgumentNullException(nameof(fileName));
+ ArgumentNullException.ThrowIfNull( fileName );
using var zipToCreate = new FileStream(fileName, FileMode.Open, FileAccess.Read);
using var zip = new ZipArchive(zipToCreate, ZipArchiveMode.Read);
var zippedNuspec = zip.Entries.Single(e => e.FullName.EndsWith(".nuspec"));
@@ -52,4 +54,16 @@ public static NuSpec FromNupkg(string fileName)
using var streamReader = new StreamReader(stream);
return FromTextReader(streamReader);
}
+
+ public static NuSpec FromAssemble(string assemblePath)
+ {
+ if (assemblePath == null) throw new ArgumentNullException(nameof(assemblePath));
+ var nuspec = Utils.GetNuspecPath(assemblePath);
+ if (nuspec != null)
+ return FromFile( nuspec );
+ var nupkg = Utils.GetNupkgPath(assemblePath);
+ if(nupkg != null)
+ return FromNupkg(nupkg);
+ return null;
+ }
}
\ No newline at end of file
diff --git a/src/DotnetThirdPartyNotices/Utils.cs b/src/DotnetThirdPartyNotices/Utils.cs
index b3bf728..ff18dfb 100644
--- a/src/DotnetThirdPartyNotices/Utils.cs
+++ b/src/DotnetThirdPartyNotices/Utils.cs
@@ -1,18 +1,47 @@
-using System.IO;
+using System;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
namespace DotnetThirdPartyNotices;
-internal static class Utils
+internal static partial class Utils
{
- public static string GetPackagePathFromAssemblyPath(string assemblyPath)
+ public static string GetNuspecPath( string assemblyPath )
{
- var parentDirectoryInfo = Directory.GetParent(assemblyPath);
- var isValid = false;
- while (parentDirectoryInfo != null && !(isValid = NuGetVersion.IsValid(parentDirectoryInfo.Name)))
- {
- parentDirectoryInfo = parentDirectoryInfo.Parent;
- }
+ var package = GetPackagePath( assemblyPath );
+ return package != null
+ ? Directory.EnumerateFiles( package, "*.nuspec", SearchOption.TopDirectoryOnly ).FirstOrDefault()
+ : null;
+ }
- return isValid ? parentDirectoryInfo.FullName : null;
+ public static string GetNupkgPath( string assemblyPath )
+ {
+ var package = GetPackagePath( assemblyPath );
+ return package != null
+ ? Directory.EnumerateFiles( package, "*.nupkg", SearchOption.TopDirectoryOnly ).FirstOrDefault()
+ : null;
}
+
+ public static string GetPackagePath( string assemblyPath )
+ {
+ var directoryParts = Path.GetDirectoryName( assemblyPath ).Split( '\\', StringSplitOptions.RemoveEmptyEntries );
+ // packages\{packageName}\{version}\lib\{targetFramework}\{packageName}.dll
+ // packages\{packageName}\{version}\runtimes\{runtime-identifier}\lib\{targetFramework}\{packageName}.dll
+ // packages\{packageName}\{version}\lib\{targetFramework}\{culture}\{packageName}.dll
+ var index = Array.FindLastIndex( directoryParts, x => NewNugetVersionRegex().IsMatch(x));
+ if (index > -1)
+ return string.Join('\\', directoryParts.Take(index + 1) );
+ // packages\{packageName}.{version}\lib\{targetFramework}\{packageName}.dll
+ index = Array.FindLastIndex(directoryParts, x => OldNugetVersionRegex().IsMatch(x));
+ if (index > -1)
+ return string.Join('\\', directoryParts.Take(index + 1));
+ return null;
+ }
+
+ [GeneratedRegex( @"^\d+.\d+.\d+\S*$", RegexOptions.None )]
+ private static partial Regex NewNugetVersionRegex();
+
+ [GeneratedRegex( @"^\S+\.\d+.\d+.\d+\S*$" )]
+ private static partial Regex OldNugetVersionRegex();
}
\ No newline at end of file