2023-04-12 16:46:32 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.Data ;
using System.IO ;
using System.Linq ;
using System.Net.Http ;
using System.Text ;
2023-05-22 16:02:25 -04:00
using System.Text.Json ;
2023-04-12 16:46:32 -04:00
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
using System.Xml ;
2024-05-22 13:17:22 -04:00
using EpicGames.Core ;
using Microsoft.Extensions.Logging ;
2023-04-12 16:46:32 -04:00
using UnrealBuildBase ;
namespace AutomationTool.Tasks
{
/// <summary>
/// Parameters for a NuGetLicenseCheck task
/// </summary>
public class NuGetLicenseCheckTaskParameters
{
/// <summary>
/// Base directory for running the command
/// </summary>
[TaskParameter]
2024-05-21 20:54:09 -04:00
public string BaseDir { get ; set ; }
2023-04-12 16:46:32 -04:00
2023-04-12 17:11:16 -04:00
/// <summary>
/// Specifies a list of packages to ignore for version checks, separated by semicolons. Optional version number may be specified with 'name@version' syntax.
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string IgnorePackages { get ; set ; }
2023-04-12 17:11:16 -04:00
2023-04-12 16:46:32 -04:00
/// <summary>
/// Directory containing allowed licenses
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public DirectoryReference LicenseDir { get ; set ; }
2024-01-10 21:42:16 -05:00
2024-01-11 09:50:17 -05:00
/// <summary>
/// Path to a csv file to write with list of packages and their licenses
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public FileReference CsvFile { get ; set ; }
2024-01-11 09:50:17 -05:00
2024-01-10 21:42:16 -05:00
/// <summary>
/// Override path to dotnet executable
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public FileReference DotNetPath { get ; set ; }
2023-04-12 16:46:32 -04:00
}
/// <summary>
2024-02-05 09:07:39 -05:00
/// Verifies which licenses are in use by nuget dependencies
2023-04-12 16:46:32 -04:00
/// </summary>
[TaskElement("NuGet-LicenseCheck", typeof(NuGetLicenseCheckTaskParameters))]
public class NuGetLicenseCheckTask : SpawnTaskBase
{
2023-05-22 16:02:25 -04:00
class LicenseConfig
{
2023-05-22 20:00:00 -04:00
public List < string > Urls { get ; set ; } = new List < string > ( ) ;
2023-05-22 16:02:25 -04:00
}
2024-05-22 13:17:22 -04:00
readonly NuGetLicenseCheckTaskParameters _parameters ;
2023-04-12 16:46:32 -04:00
/// <summary>
2024-02-05 09:07:39 -05:00
/// Construct a NuGetLicenseCheckTask task
2023-04-12 16:46:32 -04:00
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="parameters">Parameters for the task</param>
public NuGetLicenseCheckTask ( NuGetLicenseCheckTaskParameters parameters )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
_parameters = parameters ;
2023-04-12 16:46:32 -04:00
}
2023-05-23 07:45:16 -04:00
enum PackageState
{
None ,
IgnoredViaArgs ,
MissingPackageFolder ,
MissingPackageDescriptor ,
Valid ,
}
2023-04-12 16:46:32 -04:00
class PackageInfo
{
2024-05-22 13:17:22 -04:00
public string _name ;
public string _version ;
public string _projectUrl ;
public LicenseInfo _license ;
public string _licenseSource ;
public PackageState _state ;
public FileReference _descriptor ;
2023-04-12 16:46:32 -04:00
}
class LicenseInfo
{
2024-05-22 13:17:22 -04:00
public IoHash _hash ;
public string _text ;
public string _normalizedText ;
public string _extension ;
public bool _approved ;
public FileReference _file ;
2023-04-12 16:46:32 -04:00
}
2024-05-22 13:17:22 -04:00
static LicenseInfo FindOrAddLicense ( Dictionary < IoHash , LicenseInfo > licenses , string text , string extension )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
string normalizedText = text ;
normalizedText = Regex . Replace ( normalizedText , @"^\s+" , "" , RegexOptions . Multiline ) ;
normalizedText = Regex . Replace ( normalizedText , @"\s+$" , "" , RegexOptions . Multiline ) ;
normalizedText = Regex . Replace ( normalizedText , "^(?:MIT License|The MIT License \\(MIT\\))\n" , "" , RegexOptions . Multiline ) ;
normalizedText = Regex . Replace ( normalizedText , "^Copyright \\(c\\)[^\n]*\\s*(?:All rights reserved\\.?\\s*)?" , "" , RegexOptions . Multiline ) ;
normalizedText = Regex . Replace ( normalizedText , @"\s+" , " " ) ;
normalizedText = normalizedText . Trim ( ) ;
2023-04-12 16:46:32 -04:00
2024-05-22 13:17:22 -04:00
byte [ ] data = Encoding . UTF8 . GetBytes ( normalizedText ) ;
IoHash hash = IoHash . Compute ( data ) ;
2023-04-12 16:46:32 -04:00
2024-05-22 13:17:22 -04:00
LicenseInfo licenseInfo ;
if ( ! licenses . TryGetValue ( hash , out licenseInfo ) )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
licenseInfo = new LicenseInfo ( ) ;
licenseInfo . _hash = hash ;
licenseInfo . _text = text ;
licenseInfo . _normalizedText = normalizedText ;
licenseInfo . _extension = extension ;
licenses . Add ( hash , licenseInfo ) ;
2023-04-12 16:46:32 -04:00
}
2024-05-22 13:17:22 -04:00
return licenseInfo ;
2023-04-12 16:46:32 -04:00
}
/// <summary>
2024-05-22 13:17:22 -04:00
/// ExecuteAsync the task.
2023-04-12 16:46:32 -04:00
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="job">Information about the current job</param>
/// <param name="buildProducts">Set of build products produced by this node.</param>
/// <param name="tagNameToFileSet">Mapping from tag names to the set of files they include</param>
public override async Task ExecuteAsync ( JobContext job , HashSet < FileReference > buildProducts , Dictionary < string , HashSet < FileReference > > tagNameToFileSet )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
FileReference dotNetPath = _parameters . DotNetPath ? ? Unreal . DotnetPath ;
2024-01-10 21:42:16 -05:00
2024-05-22 13:17:22 -04:00
IProcessResult nuGetOutput = await ExecuteAsync ( dotNetPath . FullName , $"nuget locals global-packages --list" , logOutput : false ) ;
if ( nuGetOutput . ExitCode ! = 0 )
2023-04-12 17:53:24 -04:00
{
2024-05-22 13:17:22 -04:00
throw new AutomationException ( "DotNet terminated with an exit code indicating an error ({0})" , nuGetOutput . ExitCode ) ;
2023-04-12 17:53:24 -04:00
}
2023-04-12 16:46:32 -04:00
2024-05-22 13:17:22 -04:00
List < DirectoryReference > nuGetPackageDirs = new List < DirectoryReference > ( ) ;
foreach ( string line in nuGetOutput . Output . Split ( '\n' ) )
2023-04-12 17:53:24 -04:00
{
2024-05-22 13:17:22 -04:00
int colonIdx = line . IndexOf ( ':' , StringComparison . Ordinal ) ;
if ( colonIdx ! = - 1 )
2023-04-12 17:53:24 -04:00
{
2024-05-22 13:17:22 -04:00
DirectoryReference nuGetPackageDir = new DirectoryReference ( line . Substring ( colonIdx + 1 ) . Trim ( ) ) ;
Logger . LogInformation ( "Using NuGet package directory: {Path}" , nuGetPackageDir ) ;
nuGetPackageDirs . Add ( nuGetPackageDir ) ;
2023-04-12 17:53:24 -04:00
}
}
const string UnknownPrefix = "Unknown-" ;
2024-05-22 13:17:22 -04:00
IProcessResult packageListOutput = await ExecuteAsync ( dotNetPath . FullName , "list package --include-transitive" , workingDir : _parameters . BaseDir , logOutput : false ) ;
if ( packageListOutput . ExitCode ! = 0 )
2023-04-12 17:53:24 -04:00
{
2024-05-22 13:17:22 -04:00
throw new AutomationException ( "DotNet terminated with an exit code indicating an error ({0})" , packageListOutput . ExitCode ) ;
2023-04-12 17:53:24 -04:00
}
2024-05-22 13:17:22 -04:00
Dictionary < string , PackageInfo > packages = new Dictionary < string , PackageInfo > ( ) ;
foreach ( string line in packageListOutput . Output . Split ( '\n' ) )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
Match match = Regex . Match ( line , @"^\s*>\s*([^\s]+)\s+(?:[^\s]+\s+)?([^\s]+)\s*$" ) ;
if ( match . Success )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
PackageInfo info = new PackageInfo ( ) ;
info . _name = match . Groups [ 1 ] . Value ;
info . _version = match . Groups [ 2 ] . Value ;
packages . TryAdd ( $"{info._name}@{info._version}" , info ) ;
2023-04-12 16:46:32 -04:00
}
}
2024-05-22 13:17:22 -04:00
DirectoryReference packageRootDir = DirectoryReference . Combine ( DirectoryReference . GetSpecialFolder ( Environment . SpecialFolder . UserProfile ) , ".nuget" , "packages" ) ;
if ( ! DirectoryReference . Exists ( packageRootDir ) )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
throw new AutomationException ( "Missing NuGet package cache at {0}" , packageRootDir ) ;
2023-04-12 16:46:32 -04:00
}
2024-05-22 13:17:22 -04:00
HashSet < string > licenseUrls = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
2023-05-22 16:02:25 -04:00
2024-05-22 13:17:22 -04:00
Dictionary < IoHash , LicenseInfo > licenses = new Dictionary < IoHash , LicenseInfo > ( ) ;
if ( _parameters . LicenseDir ! = null )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
Logger . LogInformation ( "Reading allowed licenses from {LicenseDir}" , _parameters . LicenseDir ) ;
foreach ( FileReference file in DirectoryReference . EnumerateFiles ( _parameters . LicenseDir ) )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
if ( ! file . GetFileName ( ) . StartsWith ( UnknownPrefix , StringComparison . OrdinalIgnoreCase ) )
2023-04-12 16:46:32 -04:00
{
2023-05-22 20:15:37 -04:00
try
2023-05-22 16:02:25 -04:00
{
2024-05-22 13:17:22 -04:00
if ( file . HasExtension ( ".json" ) )
2023-05-22 20:15:37 -04:00
{
2024-05-22 13:17:22 -04:00
byte [ ] data = await FileReference . ReadAllBytesAsync ( file ) ;
LicenseConfig config = JsonSerializer . Deserialize < LicenseConfig > ( data , new JsonSerializerOptions { PropertyNameCaseInsensitive = true , AllowTrailingCommas = true , ReadCommentHandling = JsonCommentHandling . Skip } ) ;
licenseUrls . UnionWith ( config . Urls ) ;
2023-05-22 20:15:37 -04:00
}
2024-05-31 04:18:51 -04:00
else if ( file . HasExtension ( ".txt" ) | | file . HasExtension ( ".html" ) | | file . HasExtension ( ".md" ) )
2023-05-22 20:15:37 -04:00
{
2024-05-22 13:17:22 -04:00
string text = await FileReference . ReadAllTextAsync ( file ) ;
LicenseInfo license = FindOrAddLicense ( licenses , text , file . GetFileNameWithoutExtension ( ) ) ;
license . _file = file ;
license . _approved = true ;
2023-05-22 20:15:37 -04:00
}
2023-05-22 16:02:25 -04:00
}
2023-05-22 20:15:37 -04:00
catch ( Exception ex )
2023-05-22 16:02:25 -04:00
{
2024-05-22 13:17:22 -04:00
throw new AutomationException ( ex , $"Error parsing {file}: {ex.Message}" ) ;
2023-05-22 16:02:25 -04:00
}
2023-04-12 16:46:32 -04:00
}
}
}
2024-05-22 13:17:22 -04:00
HashSet < string > ignorePackages = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
if ( _parameters . IgnorePackages ! = null )
2023-04-12 17:11:16 -04:00
{
2024-05-22 13:17:22 -04:00
ignorePackages . UnionWith ( _parameters . IgnorePackages . Split ( ';' ) ) ;
2023-04-12 17:11:16 -04:00
}
2024-05-22 13:17:22 -04:00
Dictionary < string , LicenseInfo > licenseUrlToInfo = new Dictionary < string , LicenseInfo > ( StringComparer . OrdinalIgnoreCase ) ;
foreach ( PackageInfo info in packages . Values )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
if ( ignorePackages . Contains ( info . _name ) | | ignorePackages . Contains ( $"{info._name}@{info._version}" ) )
2023-04-12 17:11:16 -04:00
{
2024-05-22 13:17:22 -04:00
info . _state = PackageState . IgnoredViaArgs ;
2023-04-12 17:11:16 -04:00
continue ;
}
2024-05-22 13:17:22 -04:00
DirectoryReference packageDir = nuGetPackageDirs . Select ( x = > DirectoryReference . Combine ( x , info . _name . ToLowerInvariant ( ) , info . _version . ToLowerInvariant ( ) ) ) . FirstOrDefault ( x = > DirectoryReference . Exists ( x ) ) ;
if ( packageDir = = null )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
info . _state = PackageState . MissingPackageFolder ;
2023-04-12 16:46:32 -04:00
continue ;
}
2024-05-22 13:17:22 -04:00
info . _descriptor = FileReference . Combine ( packageDir , $"{info._name.ToLowerInvariant()}.nuspec" ) ;
if ( ! FileReference . Exists ( info . _descriptor ) )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
info . _state = PackageState . MissingPackageDescriptor ;
2023-04-12 16:46:32 -04:00
continue ;
}
2024-05-22 13:17:22 -04:00
using ( Stream stream = FileReference . Open ( info . _descriptor , FileMode . Open , FileAccess . Read , FileShare . Read ) )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
using XmlTextReader xmlReader = new XmlTextReader ( stream ) ;
xmlReader . Namespaces = false ;
2023-04-12 16:46:32 -04:00
2024-05-22 13:17:22 -04:00
XmlDocument xmlDocument = new XmlDocument ( ) ;
xmlDocument . Load ( xmlReader ) ;
2023-04-12 16:46:32 -04:00
2024-05-22 13:17:22 -04:00
XmlNode projectUrlNode = xmlDocument . SelectSingleNode ( "/package/metadata/projectUrl" ) ;
info . _projectUrl = projectUrlNode ? . InnerText ;
2024-01-11 09:50:17 -05:00
2024-05-22 13:17:22 -04:00
if ( info . _license = = null )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
XmlNode licenseNode = xmlDocument . SelectSingleNode ( "/package/metadata/license" ) ;
if ( licenseNode ? . Attributes [ "type" ] ? . InnerText ? . Equals ( "file" , StringComparison . Ordinal ) ? ? false )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
FileReference licenseFile = FileReference . Combine ( packageDir , licenseNode . InnerText ) ;
if ( FileReference . Exists ( licenseFile ) )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
string text = await FileReference . ReadAllTextAsync ( licenseFile ) ;
info . _license = FindOrAddLicense ( licenses , text , licenseFile . GetExtension ( ) ) ;
info . _licenseSource = licenseFile . FullName ;
2023-04-12 16:46:32 -04:00
}
}
}
2024-05-22 13:17:22 -04:00
if ( info . _license = = null )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
XmlNode licenseUrlNode = xmlDocument . SelectSingleNode ( "/package/metadata/licenseUrl" ) ;
2023-04-12 16:46:32 -04:00
2024-05-22 13:17:22 -04:00
string licenseUrl = licenseUrlNode ? . InnerText ;
if ( licenseUrl ! = null )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
licenseUrl = Regex . Replace ( licenseUrl , @"^https://github.com/(.*)/blob/(.*)$" , @"https://raw.githubusercontent.com/$1/$2" ) ;
info . _licenseSource = licenseUrl ;
2023-04-12 16:46:32 -04:00
2024-05-22 13:17:22 -04:00
if ( ! licenseUrlToInfo . TryGetValue ( licenseUrl , out info . _license ) )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
using ( HttpClient client = new HttpClient ( ) )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
using HttpResponseMessage response = await client . GetAsync ( licenseUrl ) ;
if ( ! response . IsSuccessStatusCode )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
Logger . LogError ( "Unable to fetch license from {LicenseUrl}" , licenseUrl ) ;
2023-04-12 16:46:32 -04:00
}
else
{
2024-05-22 13:17:22 -04:00
string text = await response . Content . ReadAsStringAsync ( ) ;
string type = ( response . Content . Headers . ContentType ? . MediaType = = "text/html" ) ? ".html" : ".txt" ;
info . _license = FindOrAddLicense ( licenses , text , type ) ;
if ( ! info . _license . _approved )
2023-05-22 16:02:25 -04:00
{
2024-05-22 13:17:22 -04:00
info . _license . _approved = licenseUrls . Contains ( licenseUrl ) ;
2023-05-22 16:02:25 -04:00
}
2024-05-22 13:17:22 -04:00
licenseUrlToInfo . Add ( licenseUrl , info . _license ) ;
2023-04-12 16:46:32 -04:00
}
}
}
}
}
}
2023-05-23 08:46:20 -04:00
2024-05-22 13:17:22 -04:00
info . _state = PackageState . Valid ;
2023-05-23 07:45:16 -04:00
}
2023-04-12 16:46:32 -04:00
2023-05-23 07:45:16 -04:00
Logger . LogInformation ( "Referenced Packages:" ) ;
Logger . LogInformation ( "" ) ;
2024-05-22 13:17:22 -04:00
foreach ( PackageInfo info in packages . Values . OrderBy ( x = > x . _name ) . ThenBy ( x = > x . _version ) )
2023-05-23 07:45:16 -04:00
{
2024-05-22 13:17:22 -04:00
switch ( info . _state )
2023-04-12 16:46:32 -04:00
{
2023-05-23 07:45:16 -04:00
case PackageState . IgnoredViaArgs :
2024-05-22 13:17:22 -04:00
Logger . LogInformation ( " {Name,-60} {Version,-10} Explicitly ignored via task arguments" , info . _name , info . _version ) ;
2023-05-23 07:45:16 -04:00
break ;
case PackageState . MissingPackageFolder :
2024-05-22 13:17:22 -04:00
Logger . LogInformation ( " {Name,-60} {Version,-10} NuGet package not found" , info . _name , info . _version ) ;
2023-05-23 07:45:16 -04:00
break ;
case PackageState . MissingPackageDescriptor :
2024-05-22 13:17:22 -04:00
Logger . LogWarning ( " {Name,-60} {Version,-10} Missing package descriptor: {NuSpecFile}" , info . _name , info . _version , info . _descriptor ) ;
2023-05-23 07:45:16 -04:00
break ;
case PackageState . Valid :
2024-05-22 13:17:22 -04:00
if ( info . _license = = null )
2023-05-23 07:45:16 -04:00
{
2024-05-22 13:17:22 -04:00
Logger . LogError ( " {Name,-60} {Version,-10} No license metadata found" , info . _name , info . _version ) ;
2023-05-23 07:45:16 -04:00
}
2024-05-22 13:17:22 -04:00
else if ( ! info . _license . _approved )
2023-05-23 07:45:16 -04:00
{
2024-05-22 13:17:22 -04:00
Logger . LogWarning ( " {Name,-60} {Version,-10} {Hash}" , info . _name , info . _version , info . _license . _hash ) ;
2023-05-23 07:45:16 -04:00
}
else
{
2024-05-22 13:17:22 -04:00
Logger . LogInformation ( " {Name,-60} {Version,-10} {Hash}" , info . _name , info . _version , info . _license . _hash ) ;
2023-05-23 07:45:16 -04:00
}
break ;
default :
2024-05-22 13:17:22 -04:00
Logger . LogError ( " {Name,-60} {Version,-10} Unhandled state: {State}" , info . _name , info . _version , info . _state ) ;
2023-05-23 07:45:16 -04:00
break ;
2023-04-12 16:46:32 -04:00
}
}
2024-05-22 13:17:22 -04:00
Dictionary < LicenseInfo , List < PackageInfo > > missingLicenses = new Dictionary < LicenseInfo , List < PackageInfo > > ( ) ;
foreach ( PackageInfo packageInfo in packages . Values )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
if ( packageInfo . _license ! = null & & ! packageInfo . _license . _approved )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
List < PackageInfo > licensePackages ;
if ( ! missingLicenses . TryGetValue ( packageInfo . _license , out licensePackages ) )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
licensePackages = new List < PackageInfo > ( ) ;
missingLicenses . Add ( packageInfo . _license , licensePackages ) ;
2023-04-12 16:46:32 -04:00
}
2024-05-22 13:17:22 -04:00
licensePackages . Add ( packageInfo ) ;
2023-04-12 16:46:32 -04:00
}
}
2024-05-22 13:17:22 -04:00
if ( missingLicenses . Count > 0 )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
DirectoryReference licenseDir = _parameters . LicenseDir ? ? DirectoryReference . Combine ( Unreal . RootDirectory , "Engine" , "Saved" , "Licenses" ) ;
DirectoryReference . CreateDirectory ( licenseDir ) ;
2023-04-12 16:46:32 -04:00
Logger . LogInformation ( "" ) ;
Logger . LogInformation ( "Missing licenses:" ) ;
2024-05-22 13:17:22 -04:00
foreach ( ( LicenseInfo missingLicense , List < PackageInfo > missingLicensePackages ) in missingLicenses . OrderBy ( x = > x . Key . _hash ) )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
FileReference outputFile = FileReference . Combine ( licenseDir , $"{UnknownPrefix}{missingLicense._hash}{missingLicense._extension}" ) ;
await FileReference . WriteAllTextAsync ( outputFile , missingLicense . _text ) ;
2023-04-12 16:46:32 -04:00
Logger . LogInformation ( "" ) ;
2024-05-22 13:17:22 -04:00
Logger . LogInformation ( " {LicenseFile}" , outputFile ) ;
foreach ( PackageInfo licensePackage in missingLicensePackages )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
Logger . LogInformation ( " -> {Name} {Version} ({Source})" , licensePackage . _name , licensePackage . _version , licensePackage . _licenseSource ) ;
2023-04-12 16:46:32 -04:00
}
}
}
2024-01-11 09:50:17 -05:00
2024-05-22 13:17:22 -04:00
if ( _parameters . CsvFile ! = null )
2024-01-11 09:50:17 -05:00
{
2024-05-22 13:17:22 -04:00
Logger . LogInformation ( "Writing {File}" , _parameters . CsvFile ) ;
DirectoryReference . CreateDirectory ( _parameters . CsvFile . Directory ) ;
using ( StreamWriter writer = new StreamWriter ( _parameters . CsvFile . FullName ) )
2024-01-11 09:50:17 -05:00
{
await writer . WriteLineAsync ( $"Package,Version,Project Url,License Url,License Hash,License File" ) ;
2024-05-22 13:17:22 -04:00
foreach ( PackageInfo packageInfo in packages . Values )
2024-01-11 09:50:17 -05:00
{
2024-05-22 13:17:22 -04:00
string relativeLicensePath = "" ;
if ( packageInfo . _license ? . _file ! = null )
2024-01-11 09:50:17 -05:00
{
2024-05-22 13:17:22 -04:00
relativeLicensePath = packageInfo . _license . _file . MakeRelativeTo ( _parameters . CsvFile . Directory ) ;
2024-01-11 09:50:17 -05:00
}
2024-05-22 13:17:22 -04:00
string licenseUrl = "" ;
if ( packageInfo . _licenseSource ! = null & & packageInfo . _licenseSource . StartsWith ( "http" , StringComparison . OrdinalIgnoreCase ) )
2024-01-11 09:50:17 -05:00
{
2024-05-22 13:17:22 -04:00
licenseUrl = packageInfo . _licenseSource ;
2024-01-11 09:50:17 -05:00
}
2024-05-22 13:17:22 -04:00
await writer . WriteLineAsync ( $"\" { packageInfo . _name } \ ",\"{packageInfo._version}\",{packageInfo._projectUrl},{licenseUrl},{packageInfo._license?._hash},{relativeLicensePath}" ) ;
2024-01-11 09:50:17 -05:00
}
}
}
2023-04-12 16:46:32 -04:00
}
/// <summary>
/// Output this task out to an XML writer.
/// </summary>
2024-05-22 13:17:22 -04:00
public override void Write ( XmlWriter writer )
2023-04-12 16:46:32 -04:00
{
2024-05-22 13:17:22 -04:00
Write ( writer , _parameters ) ;
2023-04-12 16:46:32 -04:00
}
/// <summary>
/// Find all the tags which are used as inputs to this task
/// </summary>
/// <returns>The tag names which are read by this task</returns>
public override IEnumerable < string > FindConsumedTagNames ( )
{
yield break ;
}
/// <summary>
/// Find all the tags which are modified by this task
/// </summary>
/// <returns>The tag names which are modified by this task</returns>
public override IEnumerable < string > FindProducedTagNames ( )
{
yield break ;
}
}
}