mirror of
https://github.com/actions/runner.git
synced 2025-12-10 04:06:57 +00:00
* hash files translation works with host context translated * Change file encoding to utf8 for PipelineTemplateConstants.cs * infer the feature flag on hash files based on the existence of the context * encoded utf8 with bom * rename host-workspace to host-work-directory and feature flag the new context var * Added comment to explain ignore host work directory resolution Co-authored-by: fhammerl <fhammerl@github.com> * trigger pipeline * Move to the github host-workspace context instead of the runner host-work-directory * remove unused imports --------- Co-authored-by: fhammerl <fhammerl@github.com>
147 lines
6.0 KiB
C#
147 lines
6.0 KiB
C#
using System;
|
|
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;
|
|
using GitHub.Runner.Common.Util;
|
|
|
|
namespace GitHub.Runner.Worker.Expressions
|
|
{
|
|
public sealed class HashFilesFunction : Function
|
|
{
|
|
private const int _hashFileTimeoutSeconds = 120;
|
|
|
|
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));
|
|
|
|
if (!githubContext.TryGetValue(PipelineTemplateConstants.HostWorkspace, out var workspace))
|
|
{
|
|
githubContext.TryGetValue(PipelineTemplateConstants.Workspace, out workspace);
|
|
}
|
|
ArgUtil.NotNull(workspace, nameof(workspace));
|
|
|
|
var workspaceData = workspace as StringContextData;
|
|
ArgUtil.NotNull(workspaceData, nameof(workspaceData));
|
|
|
|
string githubWorkspace = workspaceData.Value;
|
|
|
|
bool followSymlink = false;
|
|
List<string> patterns = new();
|
|
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;
|
|
|
|
string node = Path.Combine(runnerRoot, "externals", NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
|
|
string hashFilesScript = Path.Combine(binDir, "hashFiles");
|
|
var hashResult = string.Empty;
|
|
var p = new ProcessInvoker(new HashFilesTrace(context.Trace));
|
|
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);
|
|
|
|
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(_hashFileTimeoutSeconds)))
|
|
{
|
|
try
|
|
{
|
|
int exitCode = p.ExecuteAsync(workingDirectory: githubWorkspace,
|
|
fileName: node,
|
|
arguments: $"\"{hashFilesScript.Replace("\"", "\\\"")}\"",
|
|
environment: env,
|
|
requireExitCodeZero: false,
|
|
cancellationToken: tokenSource.Token).GetAwaiter().GetResult();
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|