Skip to content

Commit 31a0c4d

Browse files
authored
Improve the handling of jobs.<job_id>.if (#31070)
Fix #25897 Fix #30322 #29464 cannot handle some complex `if` conditions correctly because it only checks `always()` literally. In fact, it's not easy to evaluate the `if` condition on the Gitea side because evaluating it requires a series of contexts. But act_runner is able to evaluate the `if` condition before running the job (for more information, see [`gitea/act`](https://gitea.com/gitea/act/src/commit/517d11c67126bd97c88e2faabda0832fff482258/pkg/runner/run_context.go#L739-L753)) . So we can use act_runner to check the `if` condition. In this PR, how to handle a blocked job depends on its `needs` and `if`: - If not all jobs in `needs` completed successfully and the job's `if` is empty, set the job status to `StatusSkipped` - In other cases, the job status will be set to `StatusWaiting`, and then act_runner will check the `if` condition and run the job if the condition is met
1 parent e695ba4 commit 31a0c4d

File tree

2 files changed

+16
-16
lines changed

2 files changed

+16
-16
lines changed

services/actions/job_emitter.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"context"
88
"errors"
99
"fmt"
10-
"strings"
1110

1211
actions_model "code.gitea.io/gitea/models/actions"
1312
"code.gitea.io/gitea/models/db"
@@ -141,18 +140,19 @@ func (r *jobStatusResolver) resolve() map[int64]actions_model.Status {
141140
if allSucceed {
142141
ret[id] = actions_model.StatusWaiting
143142
} else {
144-
// If a job's "if" condition is "always()", the job should always run even if some of its dependencies did not succeed.
145-
// See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds
146-
always := false
143+
// Check if the job has an "if" condition
144+
hasIf := false
147145
if wfJobs, _ := jobparser.Parse(r.jobMap[id].WorkflowPayload); len(wfJobs) == 1 {
148146
_, wfJob := wfJobs[0].Job()
149-
expr := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(wfJob.If.Value, "${{"), "}}"))
150-
always = expr == "always()"
147+
hasIf = len(wfJob.If.Value) > 0
151148
}
152149

153-
if always {
150+
if hasIf {
151+
// act_runner will check the "if" condition
154152
ret[id] = actions_model.StatusWaiting
155153
} else {
154+
// If the "if" condition is empty and not all dependent jobs completed successfully,
155+
// the job should be skipped.
156156
ret[id] = actions_model.StatusSkipped
157157
}
158158
}

services/actions/job_emitter_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ func Test_jobStatusResolver_Resolve(t *testing.T) {
7171
want: map[int64]actions_model.Status{},
7272
},
7373
{
74-
name: "with ${{ always() }} condition",
74+
name: "`if` is not empty and all jobs in `needs` completed successfully",
7575
jobs: actions_model.ActionJobList{
76-
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
76+
{ID: 1, JobID: "job1", Status: actions_model.StatusSuccess, Needs: []string{}},
7777
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
7878
`
7979
name: test
@@ -82,15 +82,15 @@ jobs:
8282
job2:
8383
runs-on: ubuntu-latest
8484
needs: job1
85-
if: ${{ always() }}
85+
if: ${{ always() && needs.job1.result == 'success' }}
8686
steps:
87-
- run: echo "always run"
87+
- run: echo "will be checked by act_runner"
8888
`)},
8989
},
9090
want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
9191
},
9292
{
93-
name: "with always() condition",
93+
name: "`if` is not empty and not all jobs in `needs` completed successfully",
9494
jobs: actions_model.ActionJobList{
9595
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
9696
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
@@ -101,15 +101,15 @@ jobs:
101101
job2:
102102
runs-on: ubuntu-latest
103103
needs: job1
104-
if: always()
104+
if: ${{ always() && needs.job1.result == 'failure' }}
105105
steps:
106-
- run: echo "always run"
106+
- run: echo "will be checked by act_runner"
107107
`)},
108108
},
109109
want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
110110
},
111111
{
112-
name: "without always() condition",
112+
name: "`if` is empty and not all jobs in `needs` completed successfully",
113113
jobs: actions_model.ActionJobList{
114114
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
115115
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
@@ -121,7 +121,7 @@ jobs:
121121
runs-on: ubuntu-latest
122122
needs: job1
123123
steps:
124-
- run: echo "not always run"
124+
- run: echo "should be skipped"
125125
`)},
126126
},
127127
want: map[int64]actions_model.Status{2: actions_model.StatusSkipped},

0 commit comments

Comments
 (0)