cue-lang / cue

The home of the CUE language! Validate and define text-based and dynamic configuration
https://cuelang.org
Apache License 2.0
5.01k stars 283 forks source link

tools/flow: optional field set will lost dep #2535

Open morlay opened 1 year ago

morlay commented 1 year ago

What version of CUE are you using (cue version)?

$ cue version
v0.6.0 v0.5.0

Does this issue reproduce with the latest stable release?

Yes (v0.6.0)

What did you do?

-- main.go --
package main

import (
    "fmt"
    "log"
    "os"

    "cuelang.org/go/cue"
    "cuelang.org/go/cue/cuecontext"
    "cuelang.org/go/tools/flow"
)

func main() {
    ctx := cuecontext.New()
    f, err := os.ReadFile("x.cue")
    if err != nil {
        log.Fatal(err)
    }
    v := ctx.CompileBytes(f)
    if err := v.Err(); err != nil {
        log.Fatal(err)
    }

    controller := flow.New(&flow.Config{FindHiddenTasks: true}, v, ioTaskFunc)

    for _, t := range controller.Tasks() {
        fmt.Println("TASK", t.Path())
        for _, d := range t.Dependencies() {
            fmt.Println(" DEP", d.Path())
        }
    }
}

func ioTaskFunc(v cue.Value) (flow.Runner, error) {
    inputPath := cue.ParsePath("$task")
    input := v.LookupPath(inputPath)

    if !input.Exists() {
        return nil, nil
    }

    return flow.RunnerFunc(func(t *flow.Task) error {
        return nil
    }), nil
}
-- x.cue --
package x

actions: build: {
    _pre: #Run & {

    }

    _dep_lost: #DockerRun & {
        command: ["sh", "-c", "\(_pre.output)"]
    }

    _dep_expect: #DockerRunWorks & {
        command: ["sh", "-c", "\(_pre.output)"]
    }

}

#DockerRun: {
    command?: [...string]

    _run: #Run & {
        "command"?: command
    }

    output: _run.output
}

#DockerRunWorks: {
    command?: [...string]

    _run: #Run & {
        if command != _|_ {
            "command": command
        }
    }

    output: string
}

#Run: {
    $task: "run"
    command?: [...string]
    output: string
}

What did you expect to see?

TASK actions.build._pre
TASK actions.build._dep_lost._run
 DEP actions.build._pre
TASK actions.build._dep_expect._run
 DEP actions.build._pre

What did you see instead?

TASK actions.build._pre
TASK actions.build._dep_lost._run
TASK actions.build._dep_expect._run
 DEP actions.build._pre
rogpeppe commented 11 months ago

Here's a simpler example that demonstrates the issue:

exec cue cmd test
cmp stdout want-stdout
-- want-stdout --
ok
-- cue.mod/module.cue --
module: "example.com"

-- foo_tool.cue --
package foo

import (
    "tool/exec"
    "tool/cli"
)

command: test: {
    run: exec.Run & {
        cmd:    "mktemp -d"
        stdout: string
    }
    showResult: cli.Print & {
        $after: run
        if run.success {
            text: "ok"
        }
    }
}

I see this output:

> exec cue cmd test
[stdout]
ok
[stderr]
command.test.showResult.$after.stdout: 2 errors in empty disjunction:
command.test.showResult.$after.stdout: conflicting values "$WORK/.tmp/tmp.zDzrYp1GSu\n" and "$WORK/.tmp/tmp.PPkM3Q17vw\n":
    ./foo_tool.cue:14:11
command.test.showResult.$after.stdout: conflicting values string and null (mismatched types string and null):
    ./foo_tool.cue:9:7
    ./foo_tool.cue:11:11
    ./foo_tool.cue:14:11
    tool/exec:9:13
[exit status 1]
FAIL: /tmp/testscript1136299953/x.txtar/script.txtar:1: unexpected command failure

The if comprehension has caused the showResult task not to show up as a task, meaning that showResult.$after does look like a task, and gets executed, but then there's a conflict with the earlier result which has become concrete and then copied over to showResult.$after.

morlay commented 8 months ago

v0.7.0 still have this issue