diff --git a/src/NuGet.Config b/src/NuGet.Config
index 5816f1bb3..95143bd09 100644
--- a/src/NuGet.Config
+++ b/src/NuGet.Config
@@ -3,8 +3,6 @@
-
-
diff --git a/src/Runner.Worker/ActionRunner.cs b/src/Runner.Worker/ActionRunner.cs
index d88e1910c..c604679bc 100644
--- a/src/Runner.Worker/ActionRunner.cs
+++ b/src/Runner.Worker/ActionRunner.cs
@@ -179,17 +179,15 @@ namespace GitHub.Runner.Worker
ExecutionContext.Debug("Loading env");
var environment = new Dictionary(VarUtil.EnvironmentVariableKeyComparer);
- // Apply environment set using ##[set-env] first since these are job level env
- foreach (var env in ExecutionContext.EnvironmentVariables)
+#if OS_WINDOWS
+ var envContext = ExecutionContext.ExpressionValues["env"] as DictionaryContextData;
+#else
+ var envContext = ExecutionContext.ExpressionValues["env"] as CaseSensitiveDictionaryContextData;
+#endif
+ // Apply environment from env context, env context contains job level env and action's evn block
+ foreach (var env in envContext)
{
- environment[env.Key] = env.Value ?? string.Empty;
- }
-
- // Apply action's env block later.
- var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(Action.Environment, ExecutionContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer);
- foreach (var env in actionEnvironment)
- {
- environment[env.Key] = env.Value ?? string.Empty;
+ environment[env.Key] = env.Value.ToString();
}
// Apply action's intra-action state at last
diff --git a/src/Runner.Worker/StepsRunner.cs b/src/Runner.Worker/StepsRunner.cs
index c87bb0569..a0de59458 100644
--- a/src/Runner.Worker/StepsRunner.cs
+++ b/src/Runner.Worker/StepsRunner.cs
@@ -76,10 +76,33 @@ namespace GitHub.Runner.Worker
// Start
step.ExecutionContext.Start();
- // Set GITHUB_ACTION
+ // Populate env context for each step
+ Trace.Info("Initialize Env context for step");
+#if OS_WINDOWS
+ var envContext = new DictionaryContextData();
+#else
+ var envContext = new CaseSensitiveDictionaryContextData();
+#endif
+ step.ExecutionContext.ExpressionValues["env"] = envContext;
+ foreach (var pair in step.ExecutionContext.EnvironmentVariables)
+ {
+ envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty);
+ }
+
if (step is IActionRunner actionStep)
{
+ // Set GITHUB_ACTION
step.ExecutionContext.SetGitHubContext("action", actionStep.Action.Name);
+
+ // Evaluate and merge action's env block to env context
+ var templateTrace = step.ExecutionContext.ToTemplateTraceWriter();
+ var schema = new PipelineTemplateSchemaFactory().CreateSchema();
+ var templateEvaluator = new PipelineTemplateEvaluator(templateTrace, schema);
+ var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer);
+ foreach (var env in actionEnvironment)
+ {
+ envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
+ }
}
// Initialize scope
diff --git a/src/Sdk/DTPipelines/Pipelines/ContextData/PipelineContextDataExtensions.cs b/src/Sdk/DTPipelines/Pipelines/ContextData/PipelineContextDataExtensions.cs
index 164d57cb6..575e3e8ba 100644
--- a/src/Sdk/DTPipelines/Pipelines/ContextData/PipelineContextDataExtensions.cs
+++ b/src/Sdk/DTPipelines/Pipelines/ContextData/PipelineContextDataExtensions.cs
@@ -35,6 +35,19 @@ namespace GitHub.DistributedTask.Pipelines.ContextData
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(DictionaryContextData)}' was expected.");
}
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static CaseSensitiveDictionaryContextData AssertCaseSensitiveDictionary(
+ this PipelineContextData value,
+ String objectDescription)
+ {
+ if (value is CaseSensitiveDictionaryContextData dictionary)
+ {
+ return dictionary;
+ }
+
+ throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(CaseSensitiveDictionaryContextData)}' was expected.");
+ }
+
[EditorBrowsable(EditorBrowsableState.Never)]
public static StringContextData AssertString(
this PipelineContextData value,
diff --git a/src/Test/L0/Worker/ActionRunnerL0.cs b/src/Test/L0/Worker/ActionRunnerL0.cs
index caa593f65..b0d0c0ff4 100644
--- a/src/Test/L0/Worker/ActionRunnerL0.cs
+++ b/src/Test/L0/Worker/ActionRunnerL0.cs
@@ -314,6 +314,12 @@ namespace GitHub.Runner.Common.Tests.Worker
githubContext.Add("event", JToken.Parse("{\"foo\":\"bar\"}").ToPipelineContextData());
_context.Add("github", githubContext);
+#if OS_WINDOWS
+ _context["env"] = new DictionaryContextData();
+#else
+ _context["env"] = new CaseSensitiveDictionaryContextData();
+#endif
+
_ec = new Mock();
_ec.Setup(x => x.ExpressionValues).Returns(_context);
_ec.Setup(x => x.IntraActionState).Returns(new Dictionary());
diff --git a/src/Test/L0/Worker/StepsRunnerL0.cs b/src/Test/L0/Worker/StepsRunnerL0.cs
index d9721305e..3ed1e7804 100644
--- a/src/Test/L0/Worker/StepsRunnerL0.cs
+++ b/src/Test/L0/Worker/StepsRunnerL0.cs
@@ -19,6 +19,7 @@ namespace GitHub.Runner.Common.Tests.Worker
private Mock _ec;
private StepsRunner _stepsRunner;
private Variables _variables;
+ private Dictionary _env;
private DictionaryContextData _contexts;
private JobContext _jobContext;
private StepsContext _stepContext;
@@ -32,6 +33,11 @@ namespace GitHub.Runner.Common.Tests.Worker
_variables = new Variables(
hostContext: hc,
copy: variablesToCopy);
+ _env = new Dictionary()
+ {
+ {"env1", "1"},
+ {"test", "github_actions"}
+ };
_ec = new Mock();
_ec.SetupAllProperties();
_ec.Setup(x => x.Variables).Returns(_variables);
@@ -399,18 +405,98 @@ namespace GitHub.Runner.Common.Tests.Worker
}
}
- private Mock CreateStep(TaskResult result, string condition, Boolean continueOnError = false)
+ [Fact]
+ [Trait("Level", "L0")]
+ [Trait("Category", "Worker")]
+ public async Task StepEnvOverrideJobEnvContext()
+ {
+ using (TestHostContext hc = CreateTestContext())
+ {
+ // Arrange.
+ var env1 = new MappingToken(null, null, null);
+ env1.Add(new StringToken(null, null, null, "env1"), new StringToken(null, null, null, "100"));
+ env1.Add(new StringToken(null, null, null, "env2"), new BasicExpressionToken(null, null, null, "env.test"));
+ var step1 = CreateStep(TaskResult.Succeeded, "success()", env: env1);
+
+ _ec.Object.Result = null;
+
+ _ec.Setup(x => x.JobSteps).Returns(new Queue(new[] { step1.Object }));
+
+ // Act.
+ await _stepsRunner.RunAsync(jobContext: _ec.Object);
+
+ // Assert.
+ Assert.Equal(TaskResult.Succeeded, _ec.Object.Result ?? TaskResult.Succeeded);
+
+#if OS_WINDOWS
+ Assert.Equal("100", _ec.Object.ExpressionValues["env"].AssertDictionary("env")["env1"].AssertString("100"));
+ Assert.Equal("github_actions", _ec.Object.ExpressionValues["env"].AssertDictionary("env")["env2"].AssertString("github_actions"));
+#else
+ Assert.Equal("100", _ec.Object.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env1"].AssertString("100"));
+ Assert.Equal("github_actions", _ec.Object.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env2"].AssertString("github_actions"));
+#endif
+ }
+ }
+
+ [Fact]
+ [Trait("Level", "L0")]
+ [Trait("Category", "Worker")]
+ public async Task PopulateEnvContextForEachStep()
+ {
+ using (TestHostContext hc = CreateTestContext())
+ {
+ // Arrange.
+ var env1 = new MappingToken(null, null, null);
+ env1.Add(new StringToken(null, null, null, "env1"), new StringToken(null, null, null, "100"));
+ env1.Add(new StringToken(null, null, null, "env2"), new BasicExpressionToken(null, null, null, "env.test"));
+ var step1 = CreateStep(TaskResult.Succeeded, "success()", env: env1);
+
+ var env2 = new MappingToken(null, null, null);
+ env2.Add(new StringToken(null, null, null, "env1"), new StringToken(null, null, null, "1000"));
+ env2.Add(new StringToken(null, null, null, "env3"), new BasicExpressionToken(null, null, null, "env.test"));
+ var step2 = CreateStep(TaskResult.Succeeded, "success()", env: env2);
+
+ _ec.Object.Result = null;
+
+ _ec.Setup(x => x.JobSteps).Returns(new Queue(new[] { step1.Object, step2.Object }));
+
+ // Act.
+ await _stepsRunner.RunAsync(jobContext: _ec.Object);
+
+ // Assert.
+ Assert.Equal(TaskResult.Succeeded, _ec.Object.Result ?? TaskResult.Succeeded);
+#if OS_WINDOWS
+ Assert.Equal("1000", _ec.Object.ExpressionValues["env"].AssertDictionary("env")["env1"].AssertString("1000"));
+ Assert.Equal("github_actions", _ec.Object.ExpressionValues["env"].AssertDictionary("env")["env3"].AssertString("github_actions"));
+ Assert.False(_ec.Object.ExpressionValues["env"].AssertDictionary("env").ContainsKey("env2"));
+#else
+ Assert.Equal("1000", _ec.Object.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env1"].AssertString("1000"));
+ Assert.Equal("github_actions", _ec.Object.ExpressionValues["env"].AssertCaseSensitiveDictionary("env")["env3"].AssertString("github_actions"));
+ Assert.False(_ec.Object.ExpressionValues["env"].AssertCaseSensitiveDictionary("env").ContainsKey("env2"));
+#endif
+ }
+ }
+
+ private Mock CreateStep(TaskResult result, string condition, Boolean continueOnError = false, MappingToken env = null)
{
// Setup the step.
- var step = new Mock();
+ var step = new Mock();
step.Setup(x => x.Condition).Returns(condition);
step.Setup(x => x.ContinueOnError).Returns(new BooleanToken(null, null, null, continueOnError));
step.Setup(x => x.RunAsync()).Returns(Task.CompletedTask);
+ step.Setup(x => x.Action)
+ .Returns(new DistributedTask.Pipelines.ActionStep()
+ {
+ Name = "Test",
+ Id = Guid.NewGuid(),
+ Environment = env
+ });
// Setup the step execution context.
var stepContext = new Mock();
stepContext.SetupAllProperties();
stepContext.Setup(x => x.Variables).Returns(_variables);
+ stepContext.Setup(x => x.EnvironmentVariables).Returns(_env);
stepContext.Setup(x => x.ExpressionValues).Returns(_contexts);
stepContext.Setup(x => x.JobContext).Returns(_jobContext);
stepContext.Setup(x => x.StepsContext).Returns(_stepContext);
@@ -428,7 +514,7 @@ namespace GitHub.Runner.Common.Tests.Worker
return step;
}
- private string FormatSteps(IEnumerable> steps)
+ private string FormatSteps(IEnumerable> steps)
{
return String.Join(
" ; ",