2023-06-02 21:47:59 +02:00
using System ;
2020-01-19 10:32:13 -05:00
using System.IO ;
using GitHub.DistributedTask.Expressions2.Sdk ;
using GitHub.DistributedTask.Pipelines.ContextData ;
using GitHub.DistributedTask.Pipelines.ObjectTemplating ;
using GitHub.Runner.Sdk ;
using System.Reflection ;
using System.Threading ;
using System.Collections.Generic ;
2022-02-14 15:06:08 +01:00
using GitHub.Runner.Common.Util ;
2020-01-19 10:32:13 -05:00
2020-03-18 12:08:51 -04:00
namespace GitHub.Runner.Worker.Expressions
2020-01-19 10:32:13 -05:00
{
2020-03-18 12:08:51 -04:00
public sealed class HashFilesFunction : Function
2020-01-19 10:32:13 -05:00
{
2020-06-05 23:02:10 -04:00
private const int _hashFileTimeoutSeconds = 120 ;
2020-01-19 10:32:13 -05:00
protected sealed override Object EvaluateCore (
EvaluationContext context ,
out ResultMemory resultMemory )
{
resultMemory = null ;
var templateContext = context . State as DistributedTask . ObjectTemplating . TemplateContext ;
ArgUtil . NotNull ( templateContext , nameof ( templateContext ) ) ;
templateContext . ExpressionValues . TryGetValue ( PipelineTemplateConstants . GitHub , out var githubContextData ) ;
ArgUtil . NotNull ( githubContextData , nameof ( githubContextData ) ) ;
var githubContext = githubContextData as DictionaryContextData ;
ArgUtil . NotNull ( githubContext , nameof ( githubContext ) ) ;
2023-06-29 12:12:23 +02:00
if ( ! githubContext . TryGetValue ( PipelineTemplateConstants . HostWorkspace , out var workspace ) )
{
githubContext . TryGetValue ( PipelineTemplateConstants . Workspace , out workspace ) ;
}
ArgUtil . NotNull ( workspace , nameof ( workspace ) ) ;
2020-01-19 10:32:13 -05:00
var workspaceData = workspace as StringContextData ;
ArgUtil . NotNull ( workspaceData , nameof ( workspaceData ) ) ;
string githubWorkspace = workspaceData . Value ;
2023-06-29 12:12:23 +02:00
2020-01-19 10:32:13 -05:00
bool followSymlink = false ;
2022-10-18 10:54:08 -04:00
List < string > patterns = new ( ) ;
2020-01-19 10:32:13 -05:00
var firstParameter = true ;
foreach ( var parameter in Parameters )
{
var parameterString = parameter . Evaluate ( context ) . ConvertToString ( ) ;
if ( firstParameter )
{
firstParameter = false ;
if ( parameterString . StartsWith ( "--" ) )
{
if ( string . Equals ( parameterString , "--follow-symbolic-links" , StringComparison . OrdinalIgnoreCase ) )
{
followSymlink = true ;
continue ;
}
else
{
throw new ArgumentOutOfRangeException ( $"Invalid glob option {parameterString}, avaliable option: '--follow-symbolic-links'." ) ;
}
}
}
patterns . Add ( parameterString ) ;
}
context . Trace . Info ( $"Search root directory: '{githubWorkspace}'" ) ;
context . Trace . Info ( $"Search pattern: '{string.Join(" , ", patterns)}'" ) ;
string binDir = Path . GetDirectoryName ( Assembly . GetEntryAssembly ( ) . Location ) ;
string runnerRoot = new DirectoryInfo ( binDir ) . Parent . FullName ;
2022-02-25 20:59:02 +01:00
string node = Path . Combine ( runnerRoot , "externals" , NodeUtil . GetInternalNodeVersion ( ) , "bin" , $"node{IOUtil.ExeExtension}" ) ;
2020-01-19 10:32:13 -05:00
string hashFilesScript = Path . Combine ( binDir , "hashFiles" ) ;
var hashResult = string . Empty ;
2020-03-18 12:08:51 -04:00
var p = new ProcessInvoker ( new HashFilesTrace ( context . Trace ) ) ;
2020-01-19 10:32:13 -05:00
p . ErrorDataReceived + = ( ( _ , data ) = >
{
if ( ! string . IsNullOrEmpty ( data . Data ) & & data . Data . StartsWith ( "__OUTPUT__" ) & & data . Data . EndsWith ( "__OUTPUT__" ) )
{
hashResult = data . Data . Substring ( 10 , data . Data . Length - 20 ) ;
context . Trace . Info ( $"Hash result: '{hashResult}'" ) ;
}
else
{
context . Trace . Info ( data . Data ) ;
}
} ) ;
p . OutputDataReceived + = ( ( _ , data ) = >
{
context . Trace . Info ( data . Data ) ;
} ) ;
var env = new Dictionary < string , string > ( ) ;
if ( followSymlink )
{
env [ "followSymbolicLinks" ] = "true" ;
}
env [ "patterns" ] = string . Join ( Environment . NewLine , patterns ) ;
2020-06-05 23:02:10 -04:00
using ( var tokenSource = new CancellationTokenSource ( TimeSpan . FromSeconds ( _hashFileTimeoutSeconds ) ) )
2020-01-19 10:32:13 -05:00
{
2020-06-05 23:02:10 -04:00
try
{
int exitCode = p . ExecuteAsync ( workingDirectory : githubWorkspace ,
fileName : node ,
arguments : $"\" { hashFilesScript . Replace ( "\"" , "\\\"" ) } \ "" ,
environment : env ,
requireExitCodeZero : false ,
cancellationToken : tokenSource . Token ) . GetAwaiter ( ) . GetResult ( ) ;
2020-01-19 10:32:13 -05:00
2020-06-05 23:02:10 -04:00
if ( exitCode ! = 0 )
{
throw new InvalidOperationException ( $"hashFiles('{ExpressionUtility.StringEscape(string.Join(" , ", patterns))}') failed. Fail to hash files under directory '{githubWorkspace}'" ) ;
}
}
catch ( OperationCanceledException ) when ( tokenSource . IsCancellationRequested )
{
throw new TimeoutException ( $"hashFiles('{ExpressionUtility.StringEscape(string.Join(" , ", patterns))}') couldn't finish within {_hashFileTimeoutSeconds} seconds." ) ;
}
return hashResult ;
}
2020-01-19 10:32:13 -05:00
}
2020-03-18 12:08:51 -04:00
private sealed class HashFilesTrace : ITraceWriter
{
private GitHub . DistributedTask . Expressions2 . ITraceWriter _trace ;
public HashFilesTrace ( GitHub . DistributedTask . Expressions2 . ITraceWriter trace )
{
_trace = trace ;
}
public void Info ( string message )
{
_trace . Info ( message ) ;
}
public void Verbose ( string message )
{
_trace . Info ( message ) ;
}
}
2020-01-19 10:32:13 -05:00
}
2023-06-02 21:47:59 +02:00
}