mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-11 04:11:18 +01:00
Backport #29464 by @Zettat123 Fix #27906 According to GitHub's [documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds), a job should always run when its `if` is `always()` > If you would like a job to run even if a job it is dependent on did not succeed, use the `always()` conditional expression in `jobs.<job_id>.if`. Co-authored-by: Zettat123 <zettat123@gmail.com> (cherry picked from commit eabcfd3f7d9321fcf03e52977c178a96627a68da)
This commit is contained in:
parent
d97cd15a1b
commit
813577aee1
2 changed files with 76 additions and 1 deletions
|
@ -7,12 +7,14 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/queue"
|
"code.gitea.io/gitea/modules/queue"
|
||||||
|
|
||||||
|
"github.com/nektos/act/pkg/jobparser"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -76,12 +78,15 @@ func checkJobsOfRun(ctx context.Context, runID int64) error {
|
||||||
type jobStatusResolver struct {
|
type jobStatusResolver struct {
|
||||||
statuses map[int64]actions_model.Status
|
statuses map[int64]actions_model.Status
|
||||||
needs map[int64][]int64
|
needs map[int64][]int64
|
||||||
|
jobMap map[int64]*actions_model.ActionRunJob
|
||||||
}
|
}
|
||||||
|
|
||||||
func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver {
|
func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver {
|
||||||
idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs))
|
idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs))
|
||||||
|
jobMap := make(map[int64]*actions_model.ActionRunJob)
|
||||||
for _, job := range jobs {
|
for _, job := range jobs {
|
||||||
idToJobs[job.JobID] = append(idToJobs[job.JobID], job)
|
idToJobs[job.JobID] = append(idToJobs[job.JobID], job)
|
||||||
|
jobMap[job.ID] = job
|
||||||
}
|
}
|
||||||
|
|
||||||
statuses := make(map[int64]actions_model.Status, len(jobs))
|
statuses := make(map[int64]actions_model.Status, len(jobs))
|
||||||
|
@ -97,6 +102,7 @@ func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver {
|
||||||
return &jobStatusResolver{
|
return &jobStatusResolver{
|
||||||
statuses: statuses,
|
statuses: statuses,
|
||||||
needs: needs,
|
needs: needs,
|
||||||
|
jobMap: jobMap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,10 +140,23 @@ func (r *jobStatusResolver) resolve() map[int64]actions_model.Status {
|
||||||
if allDone {
|
if allDone {
|
||||||
if allSucceed {
|
if allSucceed {
|
||||||
ret[id] = actions_model.StatusWaiting
|
ret[id] = actions_model.StatusWaiting
|
||||||
|
} else {
|
||||||
|
// If a job's "if" condition is "always()", the job should always run even if some of its dependencies did not succeed.
|
||||||
|
// See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds
|
||||||
|
always := false
|
||||||
|
if wfJobs, _ := jobparser.Parse(r.jobMap[id].WorkflowPayload); len(wfJobs) == 1 {
|
||||||
|
_, wfJob := wfJobs[0].Job()
|
||||||
|
expr := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(wfJob.If.Value, "${{"), "}}"))
|
||||||
|
always = expr == "always()"
|
||||||
|
}
|
||||||
|
|
||||||
|
if always {
|
||||||
|
ret[id] = actions_model.StatusWaiting
|
||||||
} else {
|
} else {
|
||||||
ret[id] = actions_model.StatusSkipped
|
ret[id] = actions_model.StatusSkipped
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,62 @@ func Test_jobStatusResolver_Resolve(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: map[int64]actions_model.Status{},
|
want: map[int64]actions_model.Status{},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "with ${{ always() }} condition",
|
||||||
|
jobs: actions_model.ActionJobList{
|
||||||
|
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
|
||||||
|
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
|
||||||
|
`
|
||||||
|
name: test
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
job2:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: job1
|
||||||
|
if: ${{ always() }}
|
||||||
|
steps:
|
||||||
|
- run: echo "always run"
|
||||||
|
`)},
|
||||||
|
},
|
||||||
|
want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with always() condition",
|
||||||
|
jobs: actions_model.ActionJobList{
|
||||||
|
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
|
||||||
|
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
|
||||||
|
`
|
||||||
|
name: test
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
job2:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: job1
|
||||||
|
if: always()
|
||||||
|
steps:
|
||||||
|
- run: echo "always run"
|
||||||
|
`)},
|
||||||
|
},
|
||||||
|
want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "without always() condition",
|
||||||
|
jobs: actions_model.ActionJobList{
|
||||||
|
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
|
||||||
|
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
|
||||||
|
`
|
||||||
|
name: test
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
job2:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: job1
|
||||||
|
steps:
|
||||||
|
- run: echo "not always run"
|
||||||
|
`)},
|
||||||
|
},
|
||||||
|
want: map[int64]actions_model.Status{2: actions_model.StatusSkipped},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue