diff --git a/modules/actions/jobparser/jobparser.go b/modules/actions/jobparser/jobparser.go index aeeb19f768..04cb363dc5 100644 --- a/modules/actions/jobparser/jobparser.go +++ b/modules/actions/jobparser/jobparser.go @@ -9,9 +9,10 @@ import ( "sort" "strings" + "code.gitea.io/gitea/modules/log" + "gitea.com/gitea/runner/act/exprparser" "gitea.com/gitea/runner/act/model" - "code.gitea.io/gitea/modules/log" "go.yaml.in/yaml/v4" ) diff --git a/services/actions/matrix.go b/services/actions/matrix.go index 55cc91e2c6..0a2e41e0c0 100644 --- a/services/actions/matrix.go +++ b/services/actions/matrix.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "maps" + "sort" "strings" "time" @@ -349,6 +350,35 @@ func constructWorkflowWithNeeds(job *actions_model.ActionRunJob, taskNeeds map[s // Add the actual job we want to expand (with matrix and needs) maps.Copy(newJobs, jobsSection) + // The WorkflowPayload may contain a normalised/wrapped matrix (e.g. + // version: ["${{ fromJson(...) }}"]). Restore the original scalar expression + // from RawStrategy so jobparser.Parse() can expand it correctly with job outputs. + // Also drop the pre-baked "name" so jobparser regenerates it per matrix combination + // (e.g. "build (1)", "build (2)", …) instead of "build (Array)". + // Critically, re-add "needs" because EraseNeeds() removed them from WorkflowPayload: + // without needs, NewInterpeter builds an empty Needs context and + // "needs.generate.outputs.*" expressions can never be evaluated. + if targetJobDef, ok := newJobs[job.JobID]; ok { + if targetJobMap, ok := targetJobDef.(map[string]any); ok { + delete(targetJobMap, "name") + // Restore needs from taskNeeds keys so the expression evaluator + // can resolve needs..outputs.* references. + needsKeys := make([]string, 0, len(taskNeeds)) + for needJobID := range taskNeeds { + needsKeys = append(needsKeys, needJobID) + } + sort.Strings(needsKeys) + targetJobMap["needs"] = needsKeys + if job.RawStrategy != "" { + var rawStrategyMap map[string]any + if err := yaml.Unmarshal([]byte(job.RawStrategy), &rawStrategyMap); err == nil { + targetJobMap["strategy"] = rawStrategyMap + } + } + newJobs[job.JobID] = targetJobMap + } + } + // Construct the full workflow workflow := map[string]any{ "name": "matrix-expansion",