Skip to content

Commit

Permalink
fix expression at runs-on: may be an array (fix #164)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhysd committed Aug 13, 2022
1 parent 027e7a0 commit b19e12a
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 9 deletions.
2 changes: 2 additions & 0 deletions ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,8 @@ type Runner struct {
// Labels is list label names to select a runner to run a job. There are preset labels and user
// defined labels. Runner matching to the labels is selected.
Labels []*String
// Expression is a string when expression syntax ${{ }} is used for this section. Related issue is #164.
Expression *String
}

// WorkflowCallInput is a normal input for workflow call.
Expand Down
4 changes: 2 additions & 2 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ func (cmd *Command) Main(args []string) int {
flags := flag.NewFlagSet(args[0], flag.ContinueOnError)
flags.SetOutput(cmd.Stderr)
flags.Var(&ignorePats, "ignore", "Regular expression matching to error messages you want to ignore. This flag is repeatable")
flags.StringVar(&opts.Shellcheck, "shellcheck", "shellcheck", "Command name or file path of \"shellcheck\" external command. When empty, shellcheck integration will be disabled")
flags.StringVar(&opts.Pyflakes, "pyflakes", "pyflakes", "Command name or file path of \"pyflakes\" external command. When empty, pyflakes integration will be disabled")
flags.StringVar(&opts.Shellcheck, "shellcheck", "shellcheck", "Command name or file path of \"shellcheck\" external command. If empty, shellcheck integration will be disabled")
flags.StringVar(&opts.Pyflakes, "pyflakes", "pyflakes", "Command name or file path of \"pyflakes\" external command. If empty, pyflakes integration will be disabled")
flags.BoolVar(&opts.Oneline, "oneline", false, "Use one line per one error. Useful for reading error messages from programs")
flags.StringVar(&opts.Format, "format", "", "Custom template to format error messages in Go template syntax. See https://github.com/rhysd/actionlint/tree/main/docs/usage.md#format")
flags.StringVar(&opts.ConfigFile, "config-file", "", "File path to config file")
Expand Down
19 changes: 17 additions & 2 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ func (p *parser) parseExpression(n *yaml.Node, expecting string) *String {
return newString(n)
}

func (p *parser) mayParseExpression(n *yaml.Node) *String {
if n.Tag != "!!str" {
return nil
}
s := strings.TrimSpace(n.Value)
if !strings.HasPrefix(s, "${{") || !strings.HasSuffix(s, "}}") || strings.Count(n.Value, "${{") != 1 || strings.Count(n.Value, "}}") != 1 {
return nil
}
return newString(n)
}

func (p *parser) parseString(n *yaml.Node, allowEmpty bool) *String {
if !p.checkString(n, allowEmpty) {
return &String{"", false, posAt(n)}
Expand Down Expand Up @@ -1101,8 +1112,12 @@ func (p *parser) parseJob(id *String, n *yaml.Node) *Job {
ret.Needs = p.parseStringSequence("needs", v, false, false)
}
case "runs-on":
labels := p.parseStringOrStringSequence("runs-on", v, false, false)
ret.RunsOn = &Runner{labels}
if expr := p.mayParseExpression(v); expr != nil {
ret.RunsOn = &Runner{nil, expr}
} else {
labels := p.parseStringOrStringSequence("runs-on", v, false, false)
ret.RunsOn = &Runner{labels, nil}
}
stepsOnlyKey = k
case "permissions":
ret.Permissions = p.parsePermissions(k.Pos, v)
Expand Down
2 changes: 1 addition & 1 deletion process.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (proc *concurrentProcess) run(eg *errgroup.Group, exe string, args []string

// wait waits all goroutines started by this concurrentProcess instance finish.
func (proc *concurrentProcess) wait() {
proc.wg.Wait() // Wait for all gorotines completing to shutdown
proc.wg.Wait() // Wait for all goroutines completing to shutdown
}

// newCommandRunner creates new external command runner for given executable. The executable path
Expand Down
14 changes: 12 additions & 2 deletions rule_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,18 @@ func (rule *RuleExpression) VisitJobPre(n *Job) error {
rule.checkStrings(n.Needs)

if n.RunsOn != nil {
for _, l := range n.RunsOn.Labels {
rule.checkString(l)
if n.RunsOn.Expression != nil {
ty := rule.checkOneExpression(n.RunsOn.Expression, "runner label at \"runs-on\" section")
switch ty.(type) {
case *ArrayType, StringType, AnyType:
// OK
default:
rule.errorf(n.RunsOn.Expression.Pos, "type of expression at \"runs-on\" must be string or array but found type %q", ty.String())
}
} else {
for _, l := range n.RunsOn.Labels {
rule.checkString(l)
}
}
}

Expand Down
8 changes: 6 additions & 2 deletions rule_runner_label.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,12 @@ func (rule *RuleRunnerLabel) VisitJobPre(n *Job) error {
}

rule.compats = map[runnerOSCompat]*String{}
for _, label := range n.RunsOn.Labels {
rule.checkLabelAndConflict(label, m)
if n.RunsOn.Expression != nil {
rule.checkLabelAndConflict(n.RunsOn.Expression, m)
} else {
for _, label := range n.RunsOn.Labels {
rule.checkLabelAndConflict(label, m)
}
}

rule.compats = nil // reset
Expand Down
16 changes: 16 additions & 0 deletions testdata/ok/issue-164.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# https://github.com/rhysd/actionlint/issues/164

on: push

jobs:
build:
strategy:
matrix:
include:
- msg: hello macos
host-labels: ["self-hosted", "macOS", "X64"]
- msg: hello linux
host-labels: ["self-hosted", "linux"]
runs-on: ${{ matrix.host-labels }}
steps:
- run: echo "${{ matrix.msg }}"

0 comments on commit b19e12a

Please sign in to comment.