mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-09 04:30:16 +09:00
enhance(actions): improve matrix job titles, bump actionslib (#39485)
Bump actionslib to v1.3.0 and format matrix job titles like GitHub.
- Matrix values are listed in declaration order, nested arrays and
objects are flattened, null and empty values are skipped
- Expression-evaluated names are trimmed and fall back to the job ID
when blank
- Matrix `include` and `exclude` match keys case-insensitively and
coerce numbers like GitHub
| Matrix | Before | After |
|---|---|---|
| `v: ["a,b"]` | `job (a,b)` | `job (a,b)` |
| `v: ["a, b"]` | `job (a, b)` | `job (a, b)` |
| `v: [[a, b]]` | `job ([a b])` | `job (a, b)` |
| `os: [x], arch: [y]` | `job (y, x)` | `job (x, y)` |
| `v: [{t: a, p: "b,c"}]` | `job (map[p:b,c t:a])` | `job (a, b,c)` |
| `v: [{t: a, p: [b, c]}]` | `job (map[p:[b c] t:a])` | `job (a, b, c)`
|
---------
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
@@ -7,7 +7,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/actionslib/pkg/expreval"
|
||||
@@ -128,7 +127,7 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
|
||||
}
|
||||
}
|
||||
// Keep accepting empty exclude mappings for workflow compatibility, although GitHub rejects them.
|
||||
matrixes, err := (&model.Job{Strategy: job.Strategy.actStrategy()}).GetMatrixes()
|
||||
matrixes, err := (&model.Job{Strategy: job.Strategy.actStrategy()}).MatrixCombinations()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getMatrixes: %w", err)
|
||||
}
|
||||
@@ -169,7 +168,7 @@ func ExpandMatrixWithNeeds(jobID string, job *Job, gitCtx *model.GithubContext,
|
||||
if err := job.Strategy.resolve(expreval.New(NewInterpeter(jobID, nil, nil, gitCtx, results, vars, inputs).Evaluate)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matrixes, err := (&model.Job{Strategy: job.Strategy.actStrategy()}).GetMatrixes()
|
||||
matrixes, err := (&model.Job{Strategy: job.Strategy.actStrategy()}).MatrixCombinations()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getMatrixes: %w", err)
|
||||
}
|
||||
@@ -224,33 +223,24 @@ func replaceScalars(node *yaml.Node, replace func(string) string) {
|
||||
|
||||
// buildMatrixCombos builds one Job per matrix combination from src, baking the combination into the
|
||||
// strategy and interpolating the name, runs-on and continue-on-error with it.
|
||||
func buildMatrixCombos(jobID string, src *Job, matrixes []map[string]any, gitCtx *model.GithubContext, results map[string]*JobResult, vars map[string]string, inputs map[string]any) ([]*Job, error) {
|
||||
func buildMatrixCombos(jobID string, src *Job, matrixes []model.MatrixCombination, gitCtx *model.GithubContext, results map[string]*JobResult, vars map[string]string, inputs map[string]any) ([]*Job, error) {
|
||||
order, names := make([]int, len(matrixes)), make([]string, len(matrixes))
|
||||
for index, matrix := range matrixes {
|
||||
order[index], names[index] = index, matrixName(matrix)
|
||||
order[index], names[index] = index, matrixName(matrix.NameValues)
|
||||
}
|
||||
slices.SortStableFunc(order, func(a, b int) int { return strings.Compare(names[a], names[b]) })
|
||||
combos := make([]*Job, 0, len(matrixes))
|
||||
var err error
|
||||
for _, index := range order {
|
||||
matrix := matrixes[index]
|
||||
matrix := matrixes[index].Values
|
||||
combo := src.Clone()
|
||||
if combo.Name == "" {
|
||||
combo.Name = jobID
|
||||
}
|
||||
combo.Strategy.RawMatrix = encodeMatrix(matrix)
|
||||
replaceScalars(&combo.Strategy.RawMatrix, escapeExpressions)
|
||||
if src.Strategy.RawMatrix.Kind != 0 {
|
||||
combo.Strategy.JobIndex, combo.Strategy.JobTotal = index, len(matrixes)
|
||||
}
|
||||
evaluator := expreval.New(NewInterpeter(jobID, &combo.Strategy, matrix, gitCtx, results, vars, inputs).Evaluate)
|
||||
if len(matrix) == 0 && gitCtx != nil {
|
||||
combo.Name, err = evaluator.Interpolate(combo.Name)
|
||||
combo.Name = escapeExpressions(combo.Name)
|
||||
} else {
|
||||
combo.Name, err = nameWithMatrix(combo.Name, matrix, evaluator)
|
||||
}
|
||||
if err != nil {
|
||||
if combo.Name, err = jobName(combo.Name, jobID, names[index], evaluator, len(matrix) > 0 || gitCtx != nil); err != nil {
|
||||
return nil, fmt.Errorf("interpolate name for job %q: %w", jobID, err)
|
||||
}
|
||||
if gitCtx != nil { // callers without one don't read runs-on
|
||||
@@ -324,29 +314,36 @@ func encodeMatrix(matrix map[string]any) yaml.Node {
|
||||
return node
|
||||
}
|
||||
|
||||
func nameWithMatrix(name string, m map[string]any, evaluator expreval.Evaluator) (string, error) {
|
||||
if len(m) == 0 {
|
||||
// jobName trims names, gives plain text and lone string literals the suffix, and blank names the job ID
|
||||
func jobName(name, jobID, suffix string, evaluator expreval.Evaluator, evaluate bool) (string, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
if literal, ok := expreval.Literal(name); ok {
|
||||
if literal == "" {
|
||||
literal = jobID
|
||||
}
|
||||
return escapeExpressions(literal + suffix), nil
|
||||
}
|
||||
if !evaluate {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
if !strings.Contains(name, "${{") || !strings.Contains(name, "}}") {
|
||||
return escapeExpressions(name + " " + matrixName(m)), nil
|
||||
}
|
||||
|
||||
name, err := evaluator.Interpolate(name)
|
||||
if name = strings.TrimSpace(name); name == "" {
|
||||
name = jobID
|
||||
}
|
||||
return escapeExpressions(name), err
|
||||
}
|
||||
|
||||
func matrixName(m map[string]any) string {
|
||||
ks := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
ks = append(ks, k)
|
||||
// matrixName formats the name suffix, skipping null and empty values
|
||||
func matrixName(values []any) string {
|
||||
var names []string
|
||||
for _, value := range values {
|
||||
if name := exprparser.CoerceToString(value); name != "" {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
sort.Strings(ks)
|
||||
vs := make([]string, 0, len(m))
|
||||
for _, v := range ks {
|
||||
vs = append(vs, fmt.Sprint(m[v]))
|
||||
if len(names) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("(%s)", strings.Join(vs, ", "))
|
||||
return " (" + strings.Join(names, ", ") + ")"
|
||||
}
|
||||
|
||||
@@ -312,6 +312,48 @@ func TestParseRunsOnFromJSONKeepsWhatGitHubRejectsForTheJobToFail(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseJobNames(t *testing.T) {
|
||||
result, err := Parse([]byte(`on: push
|
||||
jobs:
|
||||
scalars:
|
||||
strategy: {matrix: {value: [[1.0, true, false, 0, 1000000000000000, '', null, {os: linux}], ['', null]]}}
|
||||
steps: [{run: echo}]
|
||||
trimmed:
|
||||
name: ' Trimmed '
|
||||
strategy: {matrix: {v: [a]}}
|
||||
steps: [{run: echo}]
|
||||
blank:
|
||||
name: ' '
|
||||
steps: [{run: echo}]
|
||||
folded:
|
||||
name: "${{ ' Folded' }}"
|
||||
strategy: {matrix: {v: [a]}}
|
||||
steps: [{run: echo}]
|
||||
computed:
|
||||
name: ${{ format(' {0} ', matrix.missing) }}
|
||||
strategy: {matrix: {v: [a]}}
|
||||
steps: [{run: echo}]
|
||||
padded:
|
||||
name: ${{ format(' {0} ', matrix.v) }}
|
||||
strategy: {matrix: {v: [a]}}
|
||||
steps: [{run: echo}]
|
||||
`), WithGitContext(&model.GithubContext{}))
|
||||
require.NoError(t, err)
|
||||
names := map[string][]string{}
|
||||
for _, parsed := range result {
|
||||
id, job := parsed.Job()
|
||||
names[id] = append(names[id], job.DisplayName())
|
||||
}
|
||||
assert.Equal(t, map[string][]string{
|
||||
"scalars": {"scalars", "scalars (1, true, false, 0, 1E+15, linux)"},
|
||||
"trimmed": {"Trimmed (a)"},
|
||||
"blank": {"blank"},
|
||||
"folded": {" Folded (a)"},
|
||||
"computed": {"computed"},
|
||||
"padded": {"a"},
|
||||
}, names)
|
||||
}
|
||||
|
||||
func TestJobFieldsWithoutMatrix(t *testing.T) {
|
||||
const workflow = `on: push
|
||||
jobs:
|
||||
|
||||
Reference in New Issue
Block a user