2022-06-22 09:56:45 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.Linq ;
using System.Reflection ;
using System.Runtime.CompilerServices ;
using System.Threading.Tasks ;
using EpicGames.BuildGraph ;
using EpicGames.BuildGraph.Expressions ;
using EpicGames.Core ;
using Microsoft.Extensions.Logging ;
using OpenTracing ;
using OpenTracing.Util ;
2022-06-24 15:18:50 -04:00
using UnrealBuildBase ;
using UnrealBuildTool ;
2022-06-22 09:56:45 -04:00
#nullable enable
namespace AutomationTool
{
/// <summary>
/// Base class for binding and executing nodes
/// </summary>
abstract class BgNodeExecutor
{
2024-05-22 13:17:22 -04:00
public abstract Task < bool > Execute ( JobContext job , Dictionary < string , HashSet < FileReference > > tagNameToFileSet ) ;
2022-06-22 09:56:45 -04:00
}
2022-06-24 15:18:50 -04:00
class BgBytecodeNodeExecutor : BgNodeExecutor
2022-06-22 09:56:45 -04:00
{
2022-06-24 15:18:50 -04:00
class BgContextImpl : BgContext
{
2024-05-22 13:17:22 -04:00
public BgContextImpl ( JobContext jobContext , Dictionary < string , HashSet < FileReference > > tagNameToFileSet )
: base ( tagNameToFileSet . ToDictionary ( x = > x . Key , x = > FileSet . FromFiles ( Unreal . RootDirectory , x . Value ) ) )
2022-06-24 15:18:50 -04:00
{
2024-05-22 13:17:22 -04:00
_ = jobContext ;
2022-06-24 15:18:50 -04:00
}
public override string Stream = > CommandUtils . P4Enabled ? CommandUtils . P4Env . Branch : "" ;
public override int Change = > CommandUtils . P4Enabled ? CommandUtils . P4Env . Changelist : 0 ;
public override int CodeChange = > CommandUtils . P4Enabled ? CommandUtils . P4Env . CodeChangelist : 0 ;
public override ( int Major , int Minor , int Patch ) EngineVersion
{
get
{
2024-05-22 13:17:22 -04:00
ReadOnlyBuildVersion current = ReadOnlyBuildVersion . Current ;
return ( current . MajorVersion , current . MinorVersion , current . PatchVersion ) ;
2022-06-24 15:18:50 -04:00
}
}
public override bool IsBuildMachine = > CommandUtils . IsBuildMachine ;
}
2024-05-22 13:17:22 -04:00
readonly BgNodeDef _node ;
2022-06-24 15:18:50 -04:00
2022-07-01 14:47:54 -04:00
public BgBytecodeNodeExecutor ( BgNodeDef node )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
_node = node ;
2022-06-22 09:56:45 -04:00
}
2024-05-21 21:47:03 -04:00
public static bool Bind ( ILogger logger )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
_ = logger ;
2022-06-24 15:18:50 -04:00
return true ;
2022-06-22 09:56:45 -04:00
}
/// <summary>
2024-05-22 13:17:22 -04:00
/// ExecuteAsync the method given in the
2022-06-22 09:56:45 -04:00
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="job"></param>
/// <param name="tagNameToFileSet"></param>
2022-06-22 09:56:45 -04:00
/// <returns></returns>
2024-05-22 13:17:22 -04:00
public override async Task < bool > Execute ( JobContext job , Dictionary < string , HashSet < FileReference > > tagNameToFileSet )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
BgThunkDef thunk = _node . Thunk ! ;
2022-07-01 14:47:54 -04:00
MethodInfo method = thunk . Method ;
2022-06-22 09:56:45 -04:00
2024-05-22 13:17:22 -04:00
HashSet < FileReference > buildProducts = tagNameToFileSet [ _node . DefaultOutput . TagName ] ;
2022-06-22 09:56:45 -04:00
2024-05-22 13:17:22 -04:00
BgContextImpl context = new BgContextImpl ( job , tagNameToFileSet ) ;
2022-06-22 09:56:45 -04:00
ParameterInfo [ ] parameters = method . GetParameters ( ) ;
object? [ ] arguments = new object [ parameters . Length ] ;
2024-05-22 13:17:22 -04:00
for ( int idx = 0 ; idx < parameters . Length ; idx + + )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
Type parameterType = parameters [ idx ] . ParameterType ;
if ( parameterType = = typeof ( BgContext ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
arguments [ idx ] = context ;
2022-06-22 09:56:45 -04:00
}
else
{
2024-05-22 13:17:22 -04:00
arguments [ idx ] = thunk . Arguments [ idx ] ;
2022-06-22 09:56:45 -04:00
}
}
2024-05-22 13:17:22 -04:00
Task task = ( Task ) method . Invoke ( null , arguments ) ! ;
await task ;
2022-06-22 09:56:45 -04:00
2024-05-22 13:17:22 -04:00
if ( _node . Outputs . Count > 0 )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
object? result = null ;
2022-06-22 09:56:45 -04:00
if ( method . ReturnType . IsGenericType & & method . ReturnType . GetGenericTypeDefinition ( ) = = typeof ( Task < > ) )
{
2024-05-22 13:17:22 -04:00
Type taskType = task . GetType ( ) ;
2024-05-22 11:01:26 -04:00
#pragma warning disable CA1849 // Task.Result synchronously blocks
2024-05-22 13:17:22 -04:00
PropertyInfo property = taskType . GetProperty ( nameof ( Task < int > . Result ) ) ! ;
2024-05-22 11:01:26 -04:00
#pragma warning restore CA1849
2024-05-22 13:17:22 -04:00
result = property ! . GetValue ( task ) ;
2022-06-22 09:56:45 -04:00
}
2024-05-22 13:17:22 -04:00
object? [ ] outputValues ;
if ( result is ITuple tuple )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
outputValues = Enumerable . Range ( 0 , tuple . Length ) . Select ( x = > tuple [ x ] ) . ToArray ( ) ;
2022-06-22 09:56:45 -04:00
}
else
{
2024-05-22 13:17:22 -04:00
outputValues = new [ ] { result } ;
2022-06-22 09:56:45 -04:00
}
2024-05-22 13:17:22 -04:00
for ( int idx = 0 ; idx < outputValues . Length ; idx + + )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
if ( outputValues [ idx ] is BgFileSetOutputExpr fileSet )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
string tagName = _node . Outputs [ idx + 1 ] . TagName ;
tagNameToFileSet [ tagName ] = new HashSet < FileReference > ( fileSet . Value . Flatten ( ) . Values ) ;
buildProducts . UnionWith ( tagNameToFileSet [ tagName ] ) ;
2022-06-22 09:56:45 -04:00
}
}
}
return true ;
}
}
/// <summary>
2022-06-24 18:30:51 -04:00
/// Implementation of <see cref="BgNodeDef"/> for graphs defined through XML syntax
2022-06-22 09:56:45 -04:00
/// </summary>
class BgScriptNodeExecutor : BgNodeExecutor
{
/// <summary>
/// The script node
/// </summary>
public BgScriptNode Node { get ; }
/// <summary>
/// List of bound task implementations
/// </summary>
2024-05-22 13:17:22 -04:00
readonly List < BgTaskImpl > _boundTasks = new List < BgTaskImpl > ( ) ;
2022-06-22 09:56:45 -04:00
/// <summary>
/// Constructor
/// </summary>
public BgScriptNodeExecutor ( BgScriptNode node )
{
Node = node ;
}
2024-07-26 20:26:08 -04:00
public async ValueTask < bool > BindAsync ( Dictionary < string , ScriptTaskBinding > nameToTask , Dictionary < string , BgNodeOutput > tagNameToNodeOutput , ILogger logger )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
bool result = true ;
foreach ( BgTask taskInfo in Node . Tasks )
2022-06-22 09:56:45 -04:00
{
2024-07-26 20:26:08 -04:00
BgTaskImpl ? boundTask = await BindTaskAsync ( taskInfo , nameToTask , tagNameToNodeOutput , logger ) ;
2022-06-22 09:56:45 -04:00
if ( boundTask = = null )
{
2024-05-22 13:17:22 -04:00
result = false ;
2022-06-22 09:56:45 -04:00
}
else
{
_boundTasks . Add ( boundTask ) ;
}
}
2024-05-22 13:17:22 -04:00
return result ;
2022-06-22 09:56:45 -04:00
}
2024-07-26 20:26:08 -04:00
async ValueTask < BgTaskImpl ? > BindTaskAsync ( BgTask taskInfo , Dictionary < string , ScriptTaskBinding > nameToTask , IReadOnlyDictionary < string , BgNodeOutput > tagNameToNodeOutput , ILogger logger )
2022-06-22 09:56:45 -04:00
{
// Get the reflection info for this element
2024-05-22 13:17:22 -04:00
ScriptTaskBinding ? task ;
if ( ! nameToTask . TryGetValue ( taskInfo . Name , out task ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
logger . LogScriptError ( taskInfo . Location , "Unknown task '{TaskName}'" , taskInfo . Name ) ;
2022-06-22 09:56:45 -04:00
return null ;
}
// Check all the required parameters are present
2024-05-22 13:17:22 -04:00
bool hasRequiredAttributes = true ;
foreach ( ScriptTaskParameterBinding parameter in task . NameToParameter . Values )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
if ( ! parameter . Optional & & ! taskInfo . Arguments . ContainsKey ( parameter . Name ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
logger . LogScriptError ( taskInfo . Location , "Missing required attribute - {AttrName}" , parameter . Name ) ;
hasRequiredAttributes = false ;
2022-06-22 09:56:45 -04:00
}
}
2024-07-26 20:26:08 -04:00
// Create a context for evaluating conditions
BgConditionContext conditionContext = new BgConditionContext ( Unreal . RootDirectory ) ;
2022-06-22 09:56:45 -04:00
// Read all the attributes into a parameters object for this task
2024-05-22 13:17:22 -04:00
object parametersObject = Activator . CreateInstance ( task . ParametersClass ) ! ;
foreach ( ( string name , string value ) in taskInfo . Arguments )
2022-06-22 09:56:45 -04:00
{
// Get the field that this attribute should be written to in the parameters object
2024-05-22 13:17:22 -04:00
ScriptTaskParameterBinding ? parameter ;
if ( ! task . NameToParameter . TryGetValue ( name , out parameter ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
logger . LogScriptError ( taskInfo . Location , "Unknown attribute '{AttrName}'" , name ) ;
2022-06-22 09:56:45 -04:00
continue ;
}
// If it's a collection type, split it into separate values
2022-12-09 11:22:43 -05:00
try
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
if ( parameter . CollectionType = = null )
2022-06-22 09:56:45 -04:00
{
2022-12-09 11:22:43 -05:00
// Parse it and assign it to the parameters object
2024-07-26 20:26:08 -04:00
object? fieldValue = await ParseValueAsync ( value , parameter . ValueType , conditionContext ) ;
2024-05-22 13:17:22 -04:00
if ( fieldValue ! = null )
2023-04-11 16:22:19 -04:00
{
2024-05-22 13:17:22 -04:00
parameter . SetValue ( parametersObject , fieldValue ) ;
2023-04-11 16:22:19 -04:00
}
2024-05-22 13:17:22 -04:00
else if ( ! parameter . Optional )
2023-04-11 16:22:19 -04:00
{
2024-05-22 13:17:22 -04:00
logger . LogScriptError ( taskInfo . Location , "Empty value for parameter '{AttrName}' is not allowed." , name ) ;
2023-04-11 16:22:19 -04:00
}
2022-06-22 09:56:45 -04:00
}
2022-12-09 11:22:43 -05:00
else
{
// Get the collection, or create one if necessary
2024-05-22 13:17:22 -04:00
object? collectionValue = parameter . GetValue ( parametersObject ) ;
if ( collectionValue = = null )
2022-12-09 11:22:43 -05:00
{
2024-05-22 13:17:22 -04:00
collectionValue = Activator . CreateInstance ( parameter . ParameterType ) ! ;
parameter . SetValue ( parametersObject , collectionValue ) ;
2022-12-09 11:22:43 -05:00
}
2022-06-22 09:56:45 -04:00
2022-12-09 11:22:43 -05:00
// Parse the values and add them to the collection
2024-05-22 13:17:22 -04:00
List < string > valueStrings = BgTaskImpl . SplitDelimitedList ( value ) ;
foreach ( string valueString in valueStrings )
2022-12-09 11:22:43 -05:00
{
2024-07-26 20:26:08 -04:00
object? elementValue = await ParseValueAsync ( valueString , parameter . ValueType , conditionContext ) ;
2024-05-22 13:17:22 -04:00
if ( elementValue ! = null )
2023-04-11 16:22:19 -04:00
{
2024-05-22 13:17:22 -04:00
parameter . CollectionType . InvokeMember ( "Add" , BindingFlags . InvokeMethod | BindingFlags . Instance | BindingFlags . Public , null , collectionValue , new object [ ] { elementValue } ) ;
2023-04-11 16:22:19 -04:00
}
2022-12-09 11:22:43 -05:00
}
2022-06-22 09:56:45 -04:00
}
}
2022-12-09 11:22:43 -05:00
catch ( Exception ex )
{
2024-05-22 13:17:22 -04:00
logger . LogScriptError ( taskInfo . Location , "Unable to parse argument {Name} from {Value}" , name , value ) ;
logger . LogDebug ( ex , "Exception while parsing argument {Name}" , name ) ;
2022-12-09 11:22:43 -05:00
}
2022-06-22 09:56:45 -04:00
}
// Construct the task
2024-05-22 13:17:22 -04:00
if ( ! hasRequiredAttributes )
2022-06-22 09:56:45 -04:00
{
return null ;
}
// Add it to the list
2024-05-22 13:17:22 -04:00
BgTaskImpl newTask = ( BgTaskImpl ) Activator . CreateInstance ( task . TaskClass , parametersObject ) ! ;
2022-06-22 09:56:45 -04:00
// Set up the source location for diagnostics
2024-05-22 13:17:22 -04:00
newTask . SourceLocation = taskInfo . Location ;
2022-06-22 09:56:45 -04:00
// Make sure all the read tags are local or listed as a dependency
2024-05-22 13:17:22 -04:00
foreach ( string readTagName in newTask . FindConsumedTagNames ( ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
BgNodeOutput ? output ;
if ( tagNameToNodeOutput . TryGetValue ( readTagName , out output ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
if ( output ! = null & & output . ProducingNode ! = Node & & ! Node . Inputs . Contains ( output ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
logger . LogScriptError ( taskInfo . Location , "The tag '{TagName}' is not a dependency of node '{Node}'" , readTagName , Node . Name ) ;
2022-06-22 09:56:45 -04:00
}
}
}
// Make sure all the written tags are local or listed as an output
2024-05-22 13:17:22 -04:00
foreach ( string modifiedTagName in newTask . FindProducedTagNames ( ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
BgNodeOutput ? output ;
if ( tagNameToNodeOutput . TryGetValue ( modifiedTagName , out output ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
if ( output ! = null & & ! Node . Outputs . Contains ( output ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
logger . LogScriptError ( taskInfo . Location , "The tag '{TagName}' is created by '{Node}', and cannot be modified downstream" , output . TagName , output . ProducingNode . Name ) ;
2022-06-22 09:56:45 -04:00
}
}
}
2024-05-22 13:17:22 -04:00
return newTask ;
2022-06-22 09:56:45 -04:00
}
/// <summary>
/// Parse a value of the given type
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="valueText">The text to parse</param>
/// <param name="valueType">Type of the value to parse</param>
2024-07-26 20:26:08 -04:00
/// <param name="context">Context for evaluating boolean expressions</param>
2022-06-22 09:56:45 -04:00
/// <returns>Value that was parsed</returns>
2024-07-26 20:26:08 -04:00
static async ValueTask < object? > ParseValueAsync ( string valueText , Type valueType , BgConditionContext context )
2022-06-22 09:56:45 -04:00
{
// Parse it and assign it to the parameters object
2024-05-22 13:17:22 -04:00
if ( valueType . IsEnum )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
return Enum . Parse ( valueType , valueText ) ;
2022-06-22 09:56:45 -04:00
}
2024-05-22 13:17:22 -04:00
else if ( valueType = = typeof ( Boolean ) )
2022-06-22 09:56:45 -04:00
{
2024-07-26 20:26:08 -04:00
return await BgCondition . EvaluateAsync ( valueText , context ) ;
2022-06-22 09:56:45 -04:00
}
2024-05-22 13:17:22 -04:00
else if ( valueType = = typeof ( FileReference ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
if ( String . IsNullOrEmpty ( valueText ) )
2023-04-11 16:22:19 -04:00
{
return null ;
}
else
{
2024-05-22 13:17:22 -04:00
return BgTaskImpl . ResolveFile ( valueText ) ;
2023-04-11 16:22:19 -04:00
}
2022-06-22 09:56:45 -04:00
}
2024-05-22 13:17:22 -04:00
else if ( valueType = = typeof ( DirectoryReference ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
if ( String . IsNullOrEmpty ( valueText ) )
2023-04-11 16:22:19 -04:00
{
return null ;
}
else
{
2024-05-22 13:17:22 -04:00
return BgTaskImpl . ResolveDirectory ( valueText ) ;
2023-04-11 16:22:19 -04:00
}
2022-06-22 09:56:45 -04:00
}
2024-05-22 13:17:22 -04:00
TypeConverter converter = TypeDescriptor . GetConverter ( valueType ) ;
if ( converter . CanConvertFrom ( typeof ( string ) ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
return converter . ConvertFromString ( valueText ) ;
2022-06-22 09:56:45 -04:00
}
else
{
2024-05-22 13:17:22 -04:00
return Convert . ChangeType ( valueText , valueType ) ;
2022-06-22 09:56:45 -04:00
}
}
/// <summary>
/// Build all the tasks for this node
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="job">Information about the current job</param>
/// <param name="tagNameToFileSet">Mapping from tag names to the set of files they include. Should be set to contain the node inputs on entry.</param>
2022-06-22 09:56:45 -04:00
/// <returns>Whether the task succeeded or not. Exiting with an exception will be caught and treated as a failure.</returns>
2024-05-22 13:17:22 -04:00
public override async Task < bool > Execute ( JobContext job , Dictionary < string , HashSet < FileReference > > tagNameToFileSet )
2022-06-22 09:56:45 -04:00
{
// Run each of the tasks in order
2024-05-22 13:17:22 -04:00
HashSet < FileReference > buildProducts = tagNameToFileSet [ Node . DefaultOutput . TagName ] ;
for ( int idx = 0 ; idx < _boundTasks . Count ; idx + + )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
using ( IScope scope = GlobalTracer . Instance . BuildSpan ( "Task" ) . WithTag ( "resource" , _boundTasks [ idx ] . GetTraceName ( ) ) . StartActive ( ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
ITaskExecutor ? executor = _boundTasks [ idx ] . GetExecutor ( ) ;
if ( executor = = null )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
// ExecuteAsync this task directly
2022-06-22 09:56:45 -04:00
try
{
2024-05-22 13:17:22 -04:00
_boundTasks [ idx ] . GetTraceMetadata ( scope . Span , "" ) ;
await _boundTasks [ idx ] . ExecuteAsync ( job , buildProducts , tagNameToFileSet ) ;
2022-06-22 09:56:45 -04:00
}
2024-05-22 13:17:22 -04:00
catch ( Exception ex )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
ExceptionUtils . AddContext ( ex , "while executing task {0}" , _boundTasks [ idx ] . GetTraceString ( ) ) ;
2024-04-19 10:39:08 -04:00
2024-05-22 13:17:22 -04:00
BgScriptLocation ? sourceLocation = _boundTasks [ idx ] . SourceLocation ;
2024-04-19 10:39:08 -04:00
if ( sourceLocation ! = null )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
ExceptionUtils . AddContext ( ex , "at {0}({1})" , sourceLocation . File , sourceLocation . LineNumber ) ;
2022-06-22 09:56:45 -04:00
}
2024-04-19 10:39:08 -04:00
2022-06-22 09:56:45 -04:00
throw ;
}
}
else
{
2024-05-22 13:17:22 -04:00
_boundTasks [ idx ] . GetTraceMetadata ( scope . Span , "1." ) ;
2022-06-22 09:56:45 -04:00
// The task has a custom executor, which may be able to execute several tasks simultaneously. Try to add the following tasks.
2024-05-22 13:17:22 -04:00
int firstIdx = idx ;
while ( idx + 1 < Node . Tasks . Count & & executor . Add ( _boundTasks [ idx + 1 ] ) )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
idx + + ;
_boundTasks [ idx ] . GetTraceMetadata ( scope . Span , string . Format ( "{0}." , 1 + idx - firstIdx ) ) ;
2022-06-22 09:56:45 -04:00
}
try
{
2024-05-22 13:17:22 -04:00
await executor . ExecuteAsync ( job , buildProducts , tagNameToFileSet ) ;
2022-06-22 09:56:45 -04:00
}
2024-05-22 13:17:22 -04:00
catch ( Exception ex )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
for ( int taskIdx = firstIdx ; taskIdx < = idx ; taskIdx + + )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
ExceptionUtils . AddContext ( ex , "while executing {0}" , _boundTasks [ taskIdx ] . GetTraceString ( ) ) ;
2022-06-22 09:56:45 -04:00
}
2024-04-19 10:39:08 -04:00
2024-05-22 13:17:22 -04:00
BgScriptLocation ? sourceLocation = _boundTasks [ firstIdx ] . SourceLocation ;
2024-04-19 10:39:08 -04:00
if ( sourceLocation ! = null )
2022-06-22 09:56:45 -04:00
{
2024-05-22 13:17:22 -04:00
ExceptionUtils . AddContext ( ex , "at {0}({1})" , sourceLocation . File , sourceLocation . LineNumber ) ;
2022-06-22 09:56:45 -04:00
}
2024-04-19 10:39:08 -04:00
2022-06-22 09:56:45 -04:00
throw ;
}
}
}
}
// Remove anything that doesn't exist, since these files weren't explicitly tagged
2024-05-22 13:17:22 -04:00
buildProducts . RemoveWhere ( x = > ! FileReference . Exists ( x ) ) ;
2022-06-22 09:56:45 -04:00
return true ;
}
}
}