feat: Add support for dynamic matrix evaluation in Gitea Actions workflows (#36564)

Adds dynamic matrix evaluation to Gitea Actions: a job's
`strategy.matrix` can be built from the outputs of the jobs it needs.

```yaml
jobs:
  generate:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set.outputs.result }}
    steps:
      - id: set
        run: echo "result=[1,2,3]" >> $GITHUB_OUTPUT

  build:
    needs: [generate]
    runs-on: ubuntu-latest
    strategy:
      matrix:
        version: ${{ fromJson(needs.generate.outputs.matrix) }}
    steps:
      - run: echo "building ${{ matrix.version }}"
```

Such a matrix cannot be expanded at planning time, so the job is planned
as a single placeholder and expanded by the job emitter once its needs
finish. Each combination is then gated by `if:` and concurrency as
usual.

- A matrix that resolves to no combination fails the job, as on GitHub.
- Expansion is capped at `MaxJobNumPerRun`.
- Workflows without a needs-dependent matrix are unaffected.

Fixes https://github.com/go-gitea/gitea/issues/25179

---------

Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
Signed-off-by: ZPascal <pascal.zimmermann@theiotstudio.com>
Co-authored-by: Claude <claude-sonnet-4-5@anthropic.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.8) <noreply@anthropic.com>
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
This commit is contained in:
Pascal Zimmermann
2026-07-28 15:59:00 +00:00
committed by GitHub
co-authored by Claude silverwind Claude bircni Zettat123
parent 717db275d5
commit 5672b1c4cf
20 changed files with 1252 additions and 69 deletions
+6
View File
@@ -186,6 +186,7 @@ func InsertRun(ctx context.Context, run *actions_model.ActionRun, content []byte
func insertRunJob(ctx context.Context, run *actions_model.ActionRun, runAttempt *actions_model.ActionRunAttempt, workflowJob *jobparser.SingleWorkflow, vars map[string]string, inputs map[string]any, slots maxParallelSlots) (*actions_model.ActionRunJob, []*actions_model.ActionRunJob, bool, error) {
id, job := workflowJob.Job()
needs := job.Needs()
isMatrixDeferred := jobparser.HasDeferredMatrix(job)
if err := workflowJob.SetJob(id, job.EraseNeeds()); err != nil {
return nil, nil, false, err
}
@@ -218,8 +219,13 @@ func insertRunJob(ctx context.Context, run *actions_model.ActionRun, runAttempt
WorkflowSourceRepoID: run.WorkflowRepoID,
WorkflowSourceCommitSHA: run.WorkflowCommitSHA,
ContinueOnError: job.GetContinueOnError(),
IsMatrixDeferred: isMatrixDeferred,
MaxParallel: parseMaxParallel(id, job.Strategy.MaxParallelString),
}
if isMatrixDeferred {
// Expansion overwrites WorkflowPayload; keep the raw payload so a rerun can re-derive the matrix.
runJob.DeferredMatrixPayload = payload
}
// Parse workflow/job permissions (no clamping here)
if perms := ExtractJobPermissionsFromWorkflow(workflowJob, job); perms != nil {
runJob.TokenPermissions = perms