@@ -143,4 +143,137 @@ public void Verbose(string message)
143143 }
144144 }
145145 }
146+
147+ public sealed class NewHashFilesFunction : GitHub . Actions . Expressions . Sdk . Function
148+ {
149+ private const int _hashFileTimeoutSeconds = 120 ;
150+
151+ protected sealed override Object EvaluateCore (
152+ GitHub . Actions . Expressions . Sdk . EvaluationContext context ,
153+ out GitHub . Actions . Expressions . Sdk . ResultMemory resultMemory )
154+ {
155+ resultMemory = null ;
156+ var templateContext = context . State as GitHub . Actions . WorkflowParser . ObjectTemplating . TemplateContext ;
157+ ArgUtil . NotNull ( templateContext , nameof ( templateContext ) ) ;
158+ templateContext . ExpressionValues . TryGetValue ( PipelineTemplateConstants . GitHub , out var githubContextData ) ;
159+ ArgUtil . NotNull ( githubContextData , nameof ( githubContextData ) ) ;
160+ var githubContext = githubContextData as GitHub . Actions . Expressions . Data . DictionaryExpressionData ;
161+ ArgUtil . NotNull ( githubContext , nameof ( githubContext ) ) ;
162+
163+ if ( ! githubContext . TryGetValue ( PipelineTemplateConstants . HostWorkspace , out var workspace ) )
164+ {
165+ githubContext . TryGetValue ( PipelineTemplateConstants . Workspace , out workspace ) ;
166+ }
167+ ArgUtil . NotNull ( workspace , nameof ( workspace ) ) ;
168+
169+ var workspaceData = workspace as GitHub . Actions . Expressions . Data . StringExpressionData ;
170+ ArgUtil . NotNull ( workspaceData , nameof ( workspaceData ) ) ;
171+
172+ string githubWorkspace = workspaceData . Value ;
173+
174+ bool followSymlink = false ;
175+ List < string > patterns = new ( ) ;
176+ var firstParameter = true ;
177+ foreach ( var parameter in Parameters )
178+ {
179+ var parameterString = parameter . Evaluate ( context ) . ConvertToString ( ) ;
180+ if ( firstParameter )
181+ {
182+ firstParameter = false ;
183+ if ( parameterString . StartsWith ( "--" ) )
184+ {
185+ if ( string . Equals ( parameterString , "--follow-symbolic-links" , StringComparison . OrdinalIgnoreCase ) )
186+ {
187+ followSymlink = true ;
188+ continue ;
189+ }
190+ else
191+ {
192+ throw new ArgumentOutOfRangeException ( $ "Invalid glob option { parameterString } , avaliable option: '--follow-symbolic-links'.") ;
193+ }
194+ }
195+ }
196+
197+ patterns . Add ( parameterString ) ;
198+ }
199+
200+ context . Trace . Info ( $ "Search root directory: '{ githubWorkspace } '") ;
201+ context . Trace . Info ( $ "Search pattern: '{ string . Join ( ", " , patterns ) } '") ;
202+
203+ string binDir = Path . GetDirectoryName ( Assembly . GetEntryAssembly ( ) . Location ) ;
204+ string runnerRoot = new DirectoryInfo ( binDir ) . Parent . FullName ;
205+
206+ string node = Path . Combine ( runnerRoot , "externals" , NodeUtil . GetInternalNodeVersion ( ) , "bin" , $ "node{ IOUtil . ExeExtension } ") ;
207+ string hashFilesScript = Path . Combine ( binDir , "hashFiles" ) ;
208+ var hashResult = string . Empty ;
209+ var p = new ProcessInvoker ( new NewHashFilesTrace ( context . Trace ) ) ;
210+ p . ErrorDataReceived += ( ( _ , data ) =>
211+ {
212+ if ( ! string . IsNullOrEmpty ( data . Data ) && data . Data . StartsWith ( "__OUTPUT__" ) && data . Data . EndsWith ( "__OUTPUT__" ) )
213+ {
214+ hashResult = data . Data . Substring ( 10 , data . Data . Length - 20 ) ;
215+ context . Trace . Info ( $ "Hash result: '{ hashResult } '") ;
216+ }
217+ else
218+ {
219+ context . Trace . Info ( data . Data ) ;
220+ }
221+ } ) ;
222+
223+ p . OutputDataReceived += ( ( _ , data ) =>
224+ {
225+ context . Trace . Info ( data . Data ) ;
226+ } ) ;
227+
228+ var env = new Dictionary < string , string > ( ) ;
229+ if ( followSymlink )
230+ {
231+ env [ "followSymbolicLinks" ] = "true" ;
232+ }
233+ env [ "patterns" ] = string . Join ( Environment . NewLine , patterns ) ;
234+
235+ using ( var tokenSource = new CancellationTokenSource ( TimeSpan . FromSeconds ( _hashFileTimeoutSeconds ) ) )
236+ {
237+ try
238+ {
239+ int exitCode = p . ExecuteAsync ( workingDirectory : githubWorkspace ,
240+ fileName : node ,
241+ arguments : $ "\" { hashFilesScript . Replace ( "\" " , "\\ \" " ) } \" ",
242+ environment : env ,
243+ requireExitCodeZero : false ,
244+ cancellationToken : tokenSource . Token ) . GetAwaiter ( ) . GetResult ( ) ;
245+
246+ if ( exitCode != 0 )
247+ {
248+ throw new InvalidOperationException ( $ "hashFiles('{ ExpressionUtility . StringEscape ( string . Join ( ", " , patterns ) ) } ') failed. Fail to hash files under directory '{ githubWorkspace } '") ;
249+ }
250+ }
251+ catch ( OperationCanceledException ) when ( tokenSource . IsCancellationRequested )
252+ {
253+ throw new TimeoutException ( $ "hashFiles('{ ExpressionUtility . StringEscape ( string . Join ( ", " , patterns ) ) } ') couldn't finish within { _hashFileTimeoutSeconds } seconds.") ;
254+ }
255+
256+ return hashResult ;
257+ }
258+ }
259+
260+ private sealed class NewHashFilesTrace : ITraceWriter
261+ {
262+ private GitHub . Actions . Expressions . ITraceWriter _trace ;
263+
264+ public NewHashFilesTrace ( GitHub . Actions . Expressions . ITraceWriter trace )
265+ {
266+ _trace = trace ;
267+ }
268+ public void Info ( string message )
269+ {
270+ _trace . Info ( message ) ;
271+ }
272+
273+ public void Verbose ( string message )
274+ {
275+ _trace . Info ( message ) ;
276+ }
277+ }
278+ }
146279}
0 commit comments