diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index eb1b0ff47..deedad36f 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -24,9 +24,123 @@ namespace GitHub.Runner.Worker.Handlers { public CompositeActionExecutionData Data { get; set; } + // TODO: Implement PrintActionDetails() public override void PrintActionDetails(ActionRunStage stage) { + // Just keep as same as ScriptHandler.cs for now + var target = Data.Steps; + var runStepInputs= target[0].Inputs; + var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator(); + var inputs = templateEvaluator.EvaluateStepInputs(runStepInputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions); + var taskManager = HostContext.GetService(); + var userInputs = new HashSet(StringComparer.OrdinalIgnoreCase); + var runValue = ""; + foreach (KeyValuePair input in inputs) + { + userInputs.Add(input.Key); + userInputs.Add(input.Value); + if (input.Key.Equals("run")) + { + runValue = input.Value; + } + } + var contents = runValue ?? string.Empty; + if (Action.Type == Pipelines.ActionSourceType.Script) + { + var firstLine = contents.TrimStart(' ', '\t', '\r', '\n'); + var firstNewLine = firstLine.IndexOfAny(new[] { '\r', '\n' }); + if (firstNewLine >= 0) + { + firstLine = firstLine.Substring(0, firstNewLine); + } + ExecutionContext.Output($"##[group]Run {firstLine}"); + } + else + { + throw new InvalidOperationException($"Invalid action type {Action.Type} for {nameof(ScriptHandler)}"); + } + + ar multiLines = contents.Replace("\r\n", "\n").TrimEnd('\n').Split('\n'); + foreach (var line in multiLines) + { + // Bright Cyan color + ExecutionContext.Output($"\x1b[36;1m{line}\x1b[0m"); + } + + string argFormat; + string shellCommand; + string shellCommandPath = null; + bool validateShellOnHost = !(StepHost is ContainerStepHost); + string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse()); + string shell = null; + if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell)) + { + // TODO: figure out how defaults interact with template later + // for now, we won't check job.defaults if we are inside a template. + if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults)) + { + runDefaults.TryGetValue("shell", out shell); + } + } + if (string.IsNullOrEmpty(shell)) + { +#if OS_WINDOWS + shellCommand = "pwsh"; + if (validateShellOnHost) + { + shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath); + if (string.IsNullOrEmpty(shellCommandPath)) + { + shellCommand = "powershell"; + Trace.Info($"Defaulting to {shellCommand}"); + shellCommandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath); + } + } +#else + shellCommand = "sh"; + if (validateShellOnHost) + { + shellCommandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath); + } +#endif + argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand); + } + else + { + var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell); + shellCommand = parsed.shellCommand; + if (validateShellOnHost) + { + shellCommandPath = WhichUtil.Which(parsed.shellCommand, true, Trace, prependPath); + } + + argFormat = $"{parsed.shellArgs}".TrimStart(); + if (string.IsNullOrEmpty(argFormat)) + { + argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand); + } + } + + if (!string.IsNullOrEmpty(shellCommandPath)) + { + ExecutionContext.Output($"shell: {shellCommandPath} {argFormat}"); + } + else + { + ExecutionContext.Output($"shell: {shellCommand} {argFormat}"); + } + + if (this.Environment?.Count > 0) + { + ExecutionContext.Output("env:"); + foreach (var env in this.Environment) + { + ExecutionContext.Output($" {env.Key}: {env.Value}"); + } + } + + ExecutionContext.Output("##[endgroup]"); } public async Task RunAsync(ActionRunStage stage) @@ -112,18 +226,8 @@ namespace GitHub.Runner.Worker.Handlers // Detect operating system for fileName + arguments + var contents = runValue ?? string.Empty; - - // Resolve the working directory. - string workingDirectory = ExecutionContext.GetGitHubContext("workspace"); - if (string.IsNullOrEmpty(workingDirectory)) - { - workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); - } - - // A lot of copying from ScriptHandler.cs => Should we just invoke the ScriptHandler in the future and just modify it - // to meet our needs for a composite action? - string shell = null; if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell)) { @@ -180,13 +284,57 @@ namespace GitHub.Runner.Worker.Handlers throw new ArgumentException("Invalid shell option. Shell must be a valid built-in (bash, sh, cmd, powershell, pwsh) or a format string containing '{0}'"); } + // We do not not the full path until we know what shell is being used, so that we can determine the file extension + var scriptFilePath = Path.Combine(tempDirectory, $"{Guid.NewGuid()}{ScriptHandlerHelpers.GetScriptFileExtension(shellCommand)}"); + var resolvedScriptPath = $"{StepHost.ResolvePathForStepHost(scriptFilePath).Replace("\"", "\\\"")}"; + // Format arg string with script path + var arguments = string.Format(argFormat, resolvedScriptPath); + // Fix up and write the script + contents = ScriptHandlerHelpers.FixUpScriptContents(shellCommand, contents); +#if OS_WINDOWS + // Normalize Windows line endings + contents = contents.Replace("\r\n", "\n").Replace("\n", "\r\n"); + var encoding = ExecutionContext.Variables.Retain_Default_Encoding && Console.InputEncoding.CodePage != 65001 + ? Console.InputEncoding + : new UTF8Encoding(false); +#else + // Don't add a BOM. It causes the script to fail on some operating systems (e.g. on Ubuntu 14). + var encoding = new UTF8Encoding(false); +#endif + // Script is written to local path (ie host) but executed relative to the StepHost, which may be a container + File.WriteAllText(scriptFilePath, contents, encoding); + + // Prepend PATH + AddPrependPathToEnvironment(); + + // expose context to environment + foreach (var context in ExecutionContext.ExpressionValues) + { + if (context.Value is IEnvironmentContextData runtimeContext && runtimeContext != null) + { + foreach (var env in runtimeContext.GetRuntimeEnvironmentVariables()) + { + Environment[env.Key] = env.Value; + } + } + } + + // dump out the command + var fileName = isContainerStepHost ? shellCommand : commandPath; +#if OS_OSX + if (Environment.ContainsKey("DYLD_INSERT_LIBRARIES")) // We don't check `isContainerStepHost` because we don't support container on macOS + { + // launch `node macOSRunInvoker.js shell args` instead of `shell args` to avoid macOS SIP remove `DYLD_INSERT_LIBRARIES` when launch process + string node12 = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "node12", "bin", $"node{IOUtil.ExeExtension}"); + string macOSRunInvoker = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "macos-run-invoker.js"); + arguments = $"\"{macOSRunInvoker.Replace("\"", "\\\"")}\" \"{fileName.Replace("\"", "\\\"")}\" {arguments}"; + fileName = node12; + } +#endif + ExecutionContext.Debug($"{fileName} {arguments}"); - // Use StepHost.ExecuteAsync() to execute code - // What does StepHost.ExecuteAsync do? - // basically just adds configuration stuff to the code for it to run and then invoke - // processInvoker.ExecuteAsync() which basically runs the process with the necessary configurations for the process. using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager)) using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager)) { @@ -211,6 +359,7 @@ namespace GitHub.Runner.Worker.Handlers ExecutionContext.Result = TaskResult.Failed; } } + } } }