Draft dotnet-thirdparty, a tool which can be used to generate third party notice files for .NET software.

This commit is contained in:
Frederik Carlier
2019-05-29 19:05:35 +02:00
parent 708c0e30c3
commit da31ef0bb9
21 changed files with 960 additions and 0 deletions

View File

@@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-rpm", "dotnet-rpm\do
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-deb", "dotnet-deb\dotnet-deb.csproj", "{CB69DD41-E3DA-4418-8A32-419E1B5642DD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-thirdparty", "dotnet-thirdparty\dotnet-thirdparty.csproj", "{3018EDCF-625E-4A1B-85BB-6C575EB74771}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -45,6 +47,10 @@ Global
{CB69DD41-E3DA-4418-8A32-419E1B5642DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB69DD41-E3DA-4418-8A32-419E1B5642DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB69DD41-E3DA-4418-8A32-419E1B5642DD}.Release|Any CPU.Build.0 = Release|Any CPU
{3018EDCF-625E-4A1B-85BB-6C575EB74771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3018EDCF-625E-4A1B-85BB-6C575EB74771}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3018EDCF-625E-4A1B-85BB-6C575EB74771}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3018EDCF-625E-4A1B-85BB-6C575EB74771}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

22
dotnet-thirdparty/GitHubLicense.cs vendored Normal file
View File

@@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace DotNet.ThirdParty
{
class GitHubLicense
{
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("spdx_id")]
public string Spdxid { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("node_id")]
public string NodeId { get; set; }
}
}

View File

@@ -0,0 +1,38 @@
using Newtonsoft.Json;
namespace DotNet.ThirdParty
{
class GitHubProjectLicense
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("path")]
public string Path { get; set; }
[JsonProperty("sha")]
public string Sha { get; set; }
[JsonProperty("size")]
public int Size { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("html_url")]
public string HtmlUrl { get; set; }
[JsonProperty("git_url")]
public string GitUrl { get; set; }
[JsonProperty("download_url")]
public string DownloadUrl { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
// Content, encoding & links are ignored
[JsonProperty("license")]
public GitHubLicense License { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace DotNet.ThirdParty
{
interface ILicenseAnalysisService
{
Task<List<ThirdPartyNotice>> AnalyzeProject(string projectAssetsPath, string nugetPackageRoot, string licenseCacheDirectory);
}
}

11
dotnet-thirdparty/ILicenseResolver.cs vendored Normal file
View File

@@ -0,0 +1,11 @@
using System.Threading.Tasks;
namespace DotNet.ThirdParty
{
interface ILicenseResolver
{
Task<License> GetSpdxLicense(string identifier, string licenseCacheFolder);
Task<License> GetLicenseFromUrl(string url, string licenseCacheFolder);
Task<License> GetLicenseFromGitHubUrl(string url, string licenseCacheFolder);
}
}

View File

@@ -0,0 +1,11 @@
namespace DotNet.ThirdParty
{
interface IProjectAssetsService
{
string ProjectAssetsPath { get; }
string NuGetPackageRoot { get; }
string IntermediateOutputPath { get; }
bool AnalyzeProjectAsset(string projectFilePath, bool restore);
}
}

14
dotnet-thirdparty/License.cs vendored Normal file
View File

@@ -0,0 +1,14 @@
namespace DotNet.ThirdParty
{
class License
{
public string Name { get; set; }
public string Text { get; set; }
public string LicenseId { get; set; }
public override string ToString()
{
return this.Name;
}
}
}

View File

@@ -0,0 +1,118 @@
using NuGet.Common;
using NuGet.Packaging.Core;
using NuGet.Packaging.Licenses;
using NuGet.ProjectModel;
using NuGet.Protocol;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Abstractions;
using System.Linq;
using System.Threading.Tasks;
namespace DotNet.ThirdParty
{
internal class LicenseAnalysisService : ILicenseAnalysisService
{
private readonly IFileSystem fileSystem;
private readonly ILicenseResolver licenseResolver;
public LicenseAnalysisService(IFileSystem fileSystem, ILicenseResolver licenseResolver)
{
this.fileSystem = fileSystem;
this.licenseResolver = licenseResolver;
}
public async Task<List<ThirdPartyNotice>> AnalyzeProject(string projectAssetsPath, string nugetPackageRoot, string licenseCacheDirectory)
{
List<ThirdPartyNotice> notices = new List<ThirdPartyNotice>();
// Load the lock file
string lockFilePath = this.fileSystem.Path.Combine(projectAssetsPath);
var lockFile = LockFileUtilities.GetLockFile(lockFilePath, NullLogger.Instance);
// TODO: Make this selection dynamic
var target = lockFile.Targets[0];
foreach (var library in target.Libraries)
{
bool isDeployed =
library.RuntimeAssemblies.Any()
|| library.NativeLibraries.Any()
|| library.ContentFiles.Any(c => c.CopyToOutput);
if (!isDeployed)
{
Debug.WriteLine($"Ignoring library {library.Name} because it has no runtime assemblies, native libraries or content files");
continue;
}
var packageIdentity = new PackageIdentity(library.Name, library.Version);
var packageInfo = LocalFolderUtility.GetPackageV3(nugetPackageRoot, packageIdentity, NullLogger.Instance);
if (packageInfo == null)
{
Debug.WriteLine($"Ignoring library {library.Name} because its NuGet package could not be found");
continue;
}
var copyright = !string.IsNullOrWhiteSpace(packageInfo.Nuspec.GetCopyright()) ? packageInfo.Nuspec.GetCopyright() : $"Copyright (c) {packageInfo.Nuspec.GetAuthors()}";
var licenseMetadata = packageInfo.Nuspec.GetLicenseMetadata();
var licenseUrl = packageInfo.Nuspec.GetLicenseUrl();
var projectUrl = packageInfo.Nuspec.GetProjectUrl();
var packageName = !string.IsNullOrWhiteSpace(packageInfo.Nuspec.GetTitle()) ? packageInfo.Nuspec.GetTitle() : packageInfo.Nuspec.GetId();
var packageUrl = packageInfo.Nuspec.GetProjectUrl();
if (copyright == null && licenseMetadata == null && licenseUrl == null)
{
Debug.WriteLine($"Ignoring library {library.Name} because no license information could be found");
continue;
}
License license = null;
if (license == null
&& licenseMetadata != null
&& licenseMetadata.LicenseExpression.Type == LicenseExpressionType.License)
{
var nugetLicense = (NuGetLicense)licenseMetadata.LicenseExpression;
license = await this.licenseResolver.GetSpdxLicense(nugetLicense.Identifier, licenseCacheDirectory).ConfigureAwait(false);
}
else if (license == null
&& licenseUrl != null
&& licenseUrl.Contains("github.com/"))
{
license = await this.licenseResolver.GetLicenseFromGitHubUrl(licenseUrl, licenseCacheDirectory).ConfigureAwait(false);
}
else if (license == null
&& projectUrl.Contains("github.com/"))
{
license = await this.licenseResolver.GetLicenseFromGitHubUrl(projectUrl, licenseCacheDirectory).ConfigureAwait(false);
}
else if (license == null
&& licenseUrl != null)
{
license = await this.licenseResolver.GetLicenseFromUrl(licenseUrl, licenseCacheDirectory).ConfigureAwait(false);
}
if (license != null)
{
notices.Add(new ThirdPartyNotice()
{
Copyright = copyright,
PackageName = packageName,
PackageUrl = packageUrl,
License = license
});
}
else
{
Debug.WriteLine($"Ignoring library {library.Name} because no license information could be found");
}
}
return notices;
}
}
}

135
dotnet-thirdparty/LicenseResolver.cs vendored Normal file
View File

@@ -0,0 +1,135 @@
using Newtonsoft.Json;
using System;
using System.IO;
using System.IO.Abstractions;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace DotNet.ThirdParty
{
class LicenseResolver : ILicenseResolver
{
private readonly IFileSystem fileSystem;
private readonly JsonSerializer serializer = new JsonSerializer();
private readonly SHA256 sha = SHA256.Create();
public LicenseResolver(IFileSystem fileSystem)
{
this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
}
public async Task<License> GetSpdxLicense(string identifier, string licenseCacheFolder)
{
var licensePath = await this.DownloadFile($"http://spdx.org/licenses/{identifier}.json", $"{identifier}.json", licenseCacheFolder).ConfigureAwait(false);
using (var stream = this.fileSystem.File.OpenRead(licensePath))
using (var streamReader = new StreamReader(stream))
using (var jsonTextReader = new JsonTextReader(streamReader))
{
var licenseEntry = serializer.Deserialize<SpdxLicenseEntry>(jsonTextReader);
return new License()
{
Name = licenseEntry.Name,
LicenseId = licenseEntry.LicenseId,
Text = licenseEntry.LicenseText
};
}
}
public async Task<License> GetLicenseFromUrl(string url, string licenseCacheFolder)
{
var licensePath = await this.DownloadFile(url, licenseCacheFolder).ConfigureAwait(false);
// Something went wrong downloading the license file. Bail out.
if (!this.fileSystem.File.Exists(licensePath))
{
return null;
}
return new License()
{
Text = this.fileSystem.File.ReadAllText(licensePath)
};
}
public async Task<License> GetLicenseFromGitHubUrl(string url, string licenseCacheFolder)
{
// Get the owner and repo
string[] parts = url.Substring(8).Split(new char[] { '/' });
var owner = parts[1];
var repo = parts[2];
var licenseUrl = $"https://api.github.com/repos/{owner}/{repo}/license";
var localName = $"github_{owner}_{repo}.json";
var licensePath = await this.DownloadFile(licenseUrl, localName, licenseCacheFolder).ConfigureAwait(false);
// Something went wrong downloading the license file. Bail out.
if (!this.fileSystem.File.Exists(licensePath))
{
return null;
}
using (var stream = this.fileSystem.File.OpenRead(licensePath))
using (var streamReader = new StreamReader(stream))
using (var jsonTextReader = new JsonTextReader(streamReader))
{
var licenseEntry = serializer.Deserialize<GitHubProjectLicense>(jsonTextReader);
if (licenseEntry.License.Spdxid != "NOASSERTION")
{
return await this.GetSpdxLicense(licenseEntry.License.Spdxid, licenseCacheFolder);
}
else
{
var licenseText = await this.DownloadFile(licenseEntry.DownloadUrl, licenseCacheFolder);
return new License()
{
Text = this.fileSystem.File.ReadAllText(licenseText)
};
};
}
}
public Task<string> DownloadFile(string url, string licenseCacheFolder)
{
var hash = BitConverter.ToString(this.sha.ComputeHash(Encoding.UTF8.GetBytes(url))).Replace("-", string.Empty);
return DownloadFile(url, hash, licenseCacheFolder);
}
public async Task<string> DownloadFile(string url, string localName, string licenseCacheFolder)
{
var localPath = this.fileSystem.Path.Combine(licenseCacheFolder, localName);
if (!this.fileSystem.File.Exists(localPath))
{
try
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("dotnet-thirdparty", "1.0.0"));
using (var response = await client.GetAsync(url).ConfigureAwait(false))
using (var remoteStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
using (var localStream = File.Open(localPath, FileMode.Create, FileAccess.Write))
{
await remoteStream.CopyToAsync(localStream).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
}
}
catch (HttpRequestException)
{
this.fileSystem.File.Delete(localPath);
return null;
}
}
return localPath;
}
}
}

132
dotnet-thirdparty/Program.cs vendored Normal file
View File

@@ -0,0 +1,132 @@
using DotNetOutdated.Exceptions;
using DotNetOutdated.Services;
using McMaster.Extensions.CommandLineUtils;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Nustache.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Threading.Tasks;
namespace DotNet.ThirdParty
{
class Program
{
static int Main(string[] args)
{
using (var services = new ServiceCollection()
.AddSingleton<IFileSystem, FileSystem>()
.AddSingleton<IProjectDiscoveryService, ProjectDiscoveryService>()
.AddSingleton<ILicenseAnalysisService, LicenseAnalysisService>()
.AddSingleton<IProjectAssetsService, ProjectAssetsService>()
.AddSingleton<ILicenseResolver, LicenseResolver>()
.BuildServiceProvider())
{
var app = new CommandLineApplication<Program>
{
ThrowOnUnexpectedArgument = true,
FullName = $"dotnet thirdparty",
LongVersionGetter = () => ThisAssembly.AssemblyInformationalVersion,
ExtendedHelpText = $"{Environment.NewLine}See https://github.com/qmfrederik/dotnet-packaging for more information"
};
app.Conventions
.UseDefaultConventions()
.UseConstructorInjection(services);
return app.Execute(args);
}
}
private readonly IFileSystem fileSystem;
private readonly IProjectDiscoveryService projectDiscoveryService;
[Argument(0, Description = "The path to a .csproj or .fsproj file, or to a directory containing a .NET Core project. " +
"If none is specified, the current directory will be used.")]
public string Path { get; set; }
[Argument(1, "--no-restore", Description = "Do not restore the project before analyzing the third party license notices.")]
public bool SkipRestore { get; set; } = true;
private readonly ILicenseAnalysisService licenseAnalysisService;
private readonly IProjectAssetsService projectAssetsService;
public Program(IFileSystem fileSystem, IProjectDiscoveryService projectDiscoveryService, IProjectAssetsService projectAssetsService, ILicenseAnalysisService licenseAnalysisService)
{
this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
this.projectDiscoveryService = projectDiscoveryService ?? throw new ArgumentNullException(nameof(projectDiscoveryService));
this.projectAssetsService = projectAssetsService ?? throw new ArgumentNullException(nameof(projectAssetsService));
this.licenseAnalysisService = licenseAnalysisService ?? throw new ArgumentNullException(nameof(licenseAnalysisService));
}
public async Task<int> OnExecute(CommandLineApplication app, IConsole console)
{
try
{
Console.WriteLine($"dotnet thirdparty ({ThisAssembly.AssemblyInformationalVersion})");
// If no path is set, use the current directory
if (string.IsNullOrEmpty(Path))
Path = this.fileSystem.Directory.GetCurrentDirectory();
// Get all the projects
console.Write("Discovering project...");
string projectFilePath = this.projectDiscoveryService.DiscoverProject(Path);
console.WriteLine();
console.Write($"Analyzing '{projectFilePath}' dependencies");
if (!this.projectAssetsService.AnalyzeProjectAsset(projectFilePath, restore: !this.SkipRestore))
{
return 1;
}
var licenseCachePath = this.fileSystem.Path.Combine(this.projectAssetsService.IntermediateOutputPath, "ThirdPartyNotices");
if (!this.fileSystem.Directory.Exists(licenseCachePath))
{
this.fileSystem.Directory.CreateDirectory(licenseCachePath);
}
var licenses = await this.licenseAnalysisService.AnalyzeProject(this.projectAssetsService.ProjectAssetsPath, this.projectAssetsService.NuGetPackageRoot, licenseCachePath).ConfigureAwait(false);
// Cache the license information in a JSON file
using (var stream = this.fileSystem.File.Open(this.fileSystem.Path.Combine(this.projectAssetsService.IntermediateOutputPath, "ThirdPartyNotices", "licenses.json"), FileMode.Create, FileAccess.Write))
using (var streamWriter = new StreamWriter(stream))
using (var jsonTextWriter = new JsonTextWriter(streamWriter))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(jsonTextWriter, licenses);
}
Helpers.Register(nameof(TitleLine), TitleLine);
// Inspiration: https://www.nexb.com/blog/oss_attribution_obligations.html
this.fileSystem.File.WriteAllText(
this.fileSystem.Path.Combine(this.projectAssetsService.IntermediateOutputPath, "ThirdPartyNotices", "THIRD-PARTY-NOTICES.TXT"),
Render.FileToString("THIRD-PARTY-NOTICES.TXT.template", licenses));
console.WriteLine();
return 0;
}
catch (CommandValidationException e)
{
console.Error.WriteLine(e.Message);
return 1;
}
}
private static void TitleLine(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
{
if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is string)
{
context.Write(TitleLine(arguments[0] as string));
}
}
private static string TitleLine(string title)
{
return new string('-', title.Length);
}
}
}

View File

@@ -0,0 +1,35 @@
using Microsoft.Build.Locator;
using MSBuildProject = Microsoft.Build.Evaluation.Project;
namespace DotNet.ThirdParty
{
class ProjectAssetsService : IProjectAssetsService
{
static ProjectAssetsService()
{
MSBuildLocator.RegisterDefaults();
}
public string ProjectAssetsPath { get; private set; }
public string NuGetPackageRoot { get; private set; }
public string IntermediateOutputPath { get; private set; }
public bool AnalyzeProjectAsset(string projectFilePath, bool restore)
{
var project = new MSBuildProject(projectFilePath);
if (restore)
{
if (!project.Build("Restore"))
{
return false;
}
}
this.ProjectAssetsPath = project.GetPropertyValue("ProjectAssetsFile");
this.NuGetPackageRoot = project.GetPropertyValue("NugetPackageRoot");
this.IntermediateOutputPath = project.GetPropertyValue("IntermediateOutputPath");
return true;
}
}
}

View File

@@ -0,0 +1,49 @@
using DotNetOutdated.Exceptions;
using System;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
namespace DotNetOutdated.Services
{
internal class ProjectDiscoveryService : IProjectDiscoveryService
{
private readonly IFileSystem _fileSystem;
public ProjectDiscoveryService(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
public string DiscoverProject(string path)
{
if (!(_fileSystem.File.Exists(path) || _fileSystem.Directory.Exists(path)))
throw new CommandValidationException(string.Format(Resources.ValidationErrorMessages.DirectoryOrFileDoesNotExist, path));
var fileAttributes = _fileSystem.File.GetAttributes(path);
// If a directory was passed in, search for a .csproj or .fsproj file
if (fileAttributes.HasFlag(FileAttributes.Directory))
{
// Try and find individual projects
var projectFiles = _fileSystem.Directory.GetFiles(path, "*.csproj").Concat(_fileSystem.Directory.GetFiles(path, "*.fsproj")).ToArray();
if (projectFiles.Length == 1)
return _fileSystem.Path.GetFullPath(projectFiles[0]);
if (projectFiles.Length > 1)
throw new CommandValidationException(string.Format(Resources.ValidationErrorMessages.DirectoryContainsMultipleProjects, path));
// At this point the path contains no projects, so throw an exception
throw new CommandValidationException(string.Format(Resources.ValidationErrorMessages.DirectoryDoesNotContainSolutionsOrProjects, path));
}
// If a .csproj or .fsproj file was passed, just return that
if ((string.Compare(_fileSystem.Path.GetExtension(path), ".csproj", StringComparison.OrdinalIgnoreCase) == 0) ||
(string.Compare(_fileSystem.Path.GetExtension(path), ".fsproj", StringComparison.OrdinalIgnoreCase) == 0))
return _fileSystem.Path.GetFullPath(path);
// At this point, we know the file passed in is not a valid project or solution
throw new CommandValidationException(string.Format(Resources.ValidationErrorMessages.FileNotAValidSolutionOrProject, path));
}
}
}

26
dotnet-thirdparty/SpdxLicenseEntry.cs vendored Normal file
View File

@@ -0,0 +1,26 @@
using Newtonsoft.Json;
using System.Collections.ObjectModel;
namespace DotNet.ThirdParty
{
class SpdxLicenseEntry
{
[JsonProperty("isDeprecatedLicenseId")]
public bool IsDeprecatedLicenseId { get; set; }
[JsonProperty("licenseText")]
public string LicenseText { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("licenseId")]
public string LicenseId { get; set; }
[JsonProperty("seeAlso")]
public Collection<string> SeeAlso { get; set; }
[JsonProperty("isOsiApproved")]
public bool IsOsiApproved { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
This software product uses third-party libraries or other resources that
may be distributed under different licenses.
In the event that we accidentally failed to list a required notice, please
bring it to our attention.
The attached notices are provided for information only.
{{#.}}
{{PackageName}}
{{TitleLine PackageName}}
{{Copyright}}
{{#License.Name}}
Licensed under the terms of {{License.Name}}.
{{/License.Name}}
{{^License.Name}}
{{License.Text}}
{{/License.Name}}
{{/.}}

15
dotnet-thirdparty/ThirdPartyNotice.cs vendored Normal file
View File

@@ -0,0 +1,15 @@
namespace DotNet.ThirdParty
{
class ThirdPartyNotice
{
public string PackageName { get; set; }
public string PackageUrl { get; set; }
public string Copyright { get; set; }
public License License { get; set; }
public override string ToString()
{
return $"{this.PackageName} - {this.License.Name}";
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace DotNetOutdated.Exceptions
{
public class CommandValidationException : Exception
{
public CommandValidationException(string message)
: base(message)
{
}
public CommandValidationException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -0,0 +1,7 @@
namespace DotNetOutdated.Services
{
internal interface IProjectDiscoveryService
{
string DiscoverProject(string path);
}
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Jerrie Pelser
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,108 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace DotNetOutdated.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class ValidationErrorMessages {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal ValidationErrorMessages() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNet.ThirdParty.dotnet_outdated.ValidationErrorMessages", typeof(ValidationErrorMessages).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Specify which project file to use because the directory &apos;{0}&apos; contains more than one..
/// </summary>
internal static string DirectoryContainsMultipleProjects {
get {
return ResourceManager.GetString("DirectoryContainsMultipleProjects", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Specify which solution file to use because the directory &apos;{0}&apos; contains more than one..
/// </summary>
internal static string DirectoryContainsMultipleSolutions {
get {
return ResourceManager.GetString("DirectoryContainsMultipleSolutions", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The directory &apos;{0}&apos; does not contain any solutions or projects..
/// </summary>
internal static string DirectoryDoesNotContainSolutionsOrProjects {
get {
return ResourceManager.GetString("DirectoryDoesNotContainSolutionsOrProjects", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The directory or file &apos;{0}&apos; does not exist..
/// </summary>
internal static string DirectoryOrFileDoesNotExist {
get {
return ResourceManager.GetString("DirectoryOrFileDoesNotExist", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The file &apos;{0}&apos; is not a valid solution or project..
/// </summary>
internal static string FileNotAValidSolutionOrProject {
get {
return ResourceManager.GetString("FileNotAValidSolutionOrProject", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="DirectoryContainsMultipleProjects" xml:space="preserve">
<value>Specify which project file to use because the directory '{0}' contains more than one.</value>
</data>
<data name="DirectoryContainsMultipleSolutions" xml:space="preserve">
<value>Specify which solution file to use because the directory '{0}' contains more than one.</value>
</data>
<data name="DirectoryDoesNotContainSolutionsOrProjects" xml:space="preserve">
<value>The directory '{0}' does not contain any solutions or projects.</value>
</data>
<data name="DirectoryOrFileDoesNotExist" xml:space="preserve">
<value>The directory or file '{0}' does not exist.</value>
</data>
<data name="FileNotAValidSolutionOrProject" xml:space="preserve">
<value>The file '{0}' is not a valid solution or project.</value>
</data>
</root>

View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RootNamespace>DotNet.ThirdParty</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.2.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
<PackageReference Include="NerdBank.GitVersioning" Version="2.3.167" PrivateAssets="All" />
<PackageReference Include="System.IO.Abstractions" Version="3.0.2" />
<PackageReference Include="NuGet.Versioning" Version="5.1.0" />
<PackageReference Include="NuGet.Configuration" Version="5.1.0" />
<PackageReference Include="NuGet.ProjectModel" Version="5.1.0" />
<PackageReference Include="Microsoft.Build" Version="15.8.166" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.8.166" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.2.2" />
<PackageReference Include="Nustache" Version="1.16.0.10" />
</ItemGroup>
<ItemGroup>
<None Update="THIRD-PARTY-NOTICES.TXT.template">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>