Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get input output list with symlinks #544

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion go/pkg/fakes/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
repb "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2"
bsgrpc "google.golang.org/genproto/googleapis/bytestream"
bspb "google.golang.org/genproto/googleapis/bytestream"
anypb "google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/anypb"
dpb "google.golang.org/protobuf/types/known/durationpb"
tspb "google.golang.org/protobuf/types/known/timestamppb"
)
Expand Down Expand Up @@ -304,6 +304,26 @@ func (f *InputFile) apply(ac *repb.ActionResult, s *Server, execRoot string) err
return nil
}

// InputSymlink to be made available to the fake action.
type InputSymlink struct {
Path string // newname
ywmei-brt1 marked this conversation as resolved.
Show resolved Hide resolved
Content string
Target string // oldname
}

// Apply puts the target file in the fake CAS and create a symlink in OS.
func (ins *InputSymlink) apply(ac *repb.ActionResult, s *Server, execRoot string) error {
inf := InputFile{Path: ins.Target, Contents: ins.Content}
if err := inf.apply(ac, s, execRoot); err != nil {
return err
}
// create a symlink from old name (target) to new name (path)
if err := os.Symlink(filepath.Join(execRoot, ins.Target), filepath.Join(execRoot, ins.Path)); err != nil {
return fmt.Errorf("error creating symlink: %w", err)
}
return nil
}

// OutputFile is to be added as an output of the fake action.
type OutputFile struct {
Path string
Expand Down
123 changes: 111 additions & 12 deletions go/pkg/tool/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@
}
showActionRes.WriteString("\nInputs\n======\n")
log.Infof("Fetching input tree from input root digest..")
inpTree, _, err := c.getInputTree(ctx, actionProto.GetInputRootDigest())
inpTree, _, _, err := c.getInputTree(ctx, actionProto.GetInputRootDigest())
if err != nil {
showActionRes.WriteString("Failed to fetch input tree:\n")
showActionRes.WriteString(err.Error())
Expand Down Expand Up @@ -759,7 +759,7 @@
return "", err
}

outputs, _, err := c.flattenTree(ctx, outDirTree)
outputs, _, _, err := c.flattenTree(ctx, outDirTree)
if err != nil {
return "", err
}
Expand All @@ -770,41 +770,41 @@
return res.String(), nil
}

func (c *Client) getInputTree(ctx context.Context, root *repb.Digest) (string, []string, error) {
func (c *Client) getInputTree(ctx context.Context, root *repb.Digest) (string, map[string]string, map[string]string, error) {
var res bytes.Buffer

dg, err := digest.NewFromProto(root)
if err != nil {
return "", nil, err
return "", nil, nil, err

Check failure on line 778 in go/pkg/tool/tool.go

View workflow job for this annotation

GitHub Actions / lint

error returned from external package is unwrapped: sig: func github.com/bazelbuild/remote-apis-sdks/go/pkg/digest.NewFromProto(dg *github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2.Digest) (github.com/bazelbuild/remote-apis-sdks/go/pkg/digest.Digest, error) (wrapcheck)
}
res.WriteString(fmt.Sprintf("[Root directory digest: %v]", dg))

dirs, err := c.GrpcClient.GetDirectoryTree(ctx, root)
if err != nil {
return "", nil, err
return "", nil, nil, err

Check failure on line 784 in go/pkg/tool/tool.go

View workflow job for this annotation

GitHub Actions / lint

error returned from external package is unwrapped: sig: func (*github.com/bazelbuild/remote-apis-sdks/go/pkg/client.Client).GetDirectoryTree(ctx context.Context, d *github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2.Digest) (result []*github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2.Directory, err error) (wrapcheck)
}
if len(dirs) == 0 {
return "", nil, fmt.Errorf("Empty directories returned by GetTree for %v", dg)
return "", nil, nil, fmt.Errorf("Empty directories returned by GetTree for %v", dg)

Check failure on line 787 in go/pkg/tool/tool.go

View workflow job for this annotation

GitHub Actions / lint

ST1005: error strings should not be capitalized (stylecheck)
}
t := &repb.Tree{
Root: dirs[0],
Children: dirs,
}
inputs, paths, err := c.flattenTree(ctx, t)
inputs, paths, symlinks, err := c.flattenTree(ctx, t)
if err != nil {
return "", nil, err
return "", nil, nil, err
}
res.WriteString("\n")
res.WriteString(inputs)

return res.String(), paths, nil
return res.String(), paths, symlinks, nil
}

func (c *Client) flattenTree(ctx context.Context, t *repb.Tree) (string, []string, error) {
func (c *Client) flattenTree(ctx context.Context, t *repb.Tree) (string, map[string]string, map[string]string, error) {
var res bytes.Buffer
outputs, err := c.GrpcClient.FlattenTree(t, "")
if err != nil {
return "", nil, err
return "", nil, nil, err

Check failure on line 807 in go/pkg/tool/tool.go

View workflow job for this annotation

GitHub Actions / lint

error returned from external package is unwrapped: sig: func (*github.com/bazelbuild/remote-apis-sdks/go/pkg/client.Client).FlattenTree(tree *github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2.Tree, rootPath string) (map[string]*github.com/bazelbuild/remote-apis-sdks/go/pkg/client.TreeOutput, error) (wrapcheck)
}
// Sort the values by path.
paths := make([]string, 0, len(outputs))
Expand All @@ -816,8 +816,11 @@
paths = append(paths, path)
}
sort.Strings(paths)
pathToDgs := make(map[string]string, len(paths))
symToDgs := map[string]string{}
for _, path := range paths {
output := outputs[path]
dg := output.Digest
var np string
if output.NodeProperties != nil {
np = fmt.Sprintf(" [Node properties: %v]", prototext.MarshalOptions{Multiline: false}.Format(output.NodeProperties))
Expand All @@ -826,11 +829,18 @@
res.WriteString(fmt.Sprintf("%v: [Directory digest: %v]%s\n", path, output.Digest, np))
} else if output.SymlinkTarget != "" {
res.WriteString(fmt.Sprintf("%v: [Symlink digest: %v, Symlink Target: %v]%s\n", path, output.Digest, output.SymlinkTarget, np))
path = path + "->" + output.SymlinkTarget
if o, ok := outputs[output.SymlinkTarget]; ok {
dg = o.Digest
}
symToDgs[path] = fmt.Sprintf("%v", dg)
continue
} else {
res.WriteString(fmt.Sprintf("%v: [File digest: %v]%s\n", path, output.Digest, np))
}
pathToDgs[path] = fmt.Sprintf("%v", dg)
}
return res.String(), paths, nil
return res.String(), pathToDgs, symToDgs, nil
}

func (c *Client) getActionResult(ctx context.Context, actionDigest string) (*repb.ActionResult, error) {
Expand All @@ -848,3 +858,92 @@
}
return resPb, nil
}

// IO is a collection input root Digest, Inputs, and Outputs for an action.
type IO struct {
ywmei-brt1 marked this conversation as resolved.
Show resolved Hide resolved
RootDg string
Inputs *Inputs
Outputs *Outputs
}

// Inputs are input Paths (with digests) of an action. For a symlink, the key is
// `<path>-><target>`, the value is the dg of the target file.
type Inputs struct {
Paths map[string]string
PathSymlinks map[string]string
}

// Outputs are output Files/Dirs (with digests) of an action. For a symlink, the
// key is `<path>-><target>`, the value is the dg of the target file.
type Outputs struct {
Files map[string]string
Dirs map[string]string
FileSymlinks map[string]string
DirSymlinks map[string]string
}

// GetIO returns the Inputs and Outputs of an action Digest.
func (c *Client) GetIO(ctx context.Context, actionDigest string) (*IO, error) {
ywmei-brt1 marked this conversation as resolved.
Show resolved Hide resolved
acDg, err := digest.NewFromString(actionDigest)
if err != nil {
return nil, fmt.Errorf("error creating action digest: %w", err)
}
actionProto := &repb.Action{}
// Get all the Inputs.
ywmei-brt1 marked this conversation as resolved.
Show resolved Hide resolved
if _, err := c.GrpcClient.ReadProto(ctx, acDg, actionProto); err != nil {
return nil, fmt.Errorf("error reading from proto: %w", err)
}
rootDg, err := digest.NewFromProto(actionProto.GetInputRootDigest())
if err != nil {
return nil, fmt.Errorf("error getting input root digest: %w", err)
}
_, inputPaths, inputSymlinks, err := c.getInputTree(ctx, actionProto.GetInputRootDigest())
if err != nil {
return nil, err
}
// Get all the Outputs.
resPb, err := c.getActionResult(ctx, actionDigest)
if err != nil {
return nil, err
ywmei-brt1 marked this conversation as resolved.
Show resolved Hide resolved
}

files := map[string]string{}
dirs := map[string]string{}
fileSymlinks := map[string]string{}
dirSymlinks := map[string]string{}
for _, f := range resPb.GetOutputFiles() {
if f != nil {
dg, err := digest.NewFromProto(f.GetDigest())
if err != nil {
log.Errorf("error creating Digest from proto %v: %w", f.GetDigest(), err)
}
files[f.GetPath()] = fmt.Sprintf("%v", dg)
}
}
for _, d := range resPb.GetOutputDirectories() {
if d != nil {
dg, err := digest.NewFromProto(d.GetTreeDigest())
if err != nil {
log.Errorf("error creating Digest from proto %v: %w", d.GetTreeDigest(), err)
}
dirs[d.GetPath()] = fmt.Sprintf("%v", dg)
}
}
for _, fs := range resPb.GetOutputFileSymlinks() {
if fs != nil {
fileSymlinks[fs.GetPath()+"->"+fs.GetTarget()] = files[fs.GetTarget()]
}
}
for _, ds := range resPb.GetOutputDirectorySymlinks() {
if ds != nil {
dirSymlinks[ds.GetPath()+"->"+ds.GetTarget()] = dirs[ds.GetTarget()]
}
}

return &IO{
RootDg: fmt.Sprintf("%v", rootDg),
Inputs: &Inputs{Paths: inputPaths, PathSymlinks: inputSymlinks},
Outputs: &Outputs{Files: files, Dirs: dirs, FileSymlinks: fileSymlinks, DirSymlinks: dirSymlinks},
}, nil

}
59 changes: 59 additions & 0 deletions go/pkg/tool/tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,3 +444,62 @@ func TestTool_UploadBlob(t *testing.T) {
t.Fatalf("Expected 1 write for blob '%v', got %v", dg.String(), cas.BlobWrites(dg))
}
}

func TestTool_GetIO(t *testing.T) {
e, cleanup := fakes.NewTestEnv(t)
defer cleanup()
cmd := &command.Command{
Args: []string{"tool"},
ExecRoot: e.ExecRoot,
InputSpec: &command.InputSpec{
Inputs: []string{"foo.c", "bar.c"},
SymlinkBehavior: command.PreserveSymlink,
},
}
opt := command.DefaultExecutionOptions()
_, acDg, _, _ := e.Set(
cmd,
opt,
&command.Result{Status: command.CacheHitResultStatus},
&fakes.InputFile{Path: "foo.c", Contents: "foo"},
&fakes.InputSymlink{Path: "bar.c", Content: "bar", Target: "previous_dir/old_target_file"},
&fakes.OutputFile{Path: "a/b/out", Contents: "foo"},
&fakes.OutputSymlink{Path: "a/b/sl", Target: "a/b/out"},
)
toolClient := &Client{GrpcClient: e.Client.GrpcClient}
tmpDir := t.TempDir()
io, err := toolClient.GetIO(context.Background(), acDg.String())
if err != nil {
t.Fatalf("DownloadActionResult(%v,%v) failed: %v", acDg.String(), tmpDir, err)
}

getInputRootDg := io.RootDg
wantInputRootDg := "ea520b417ef2887249b4ea2d24ff64f5d7d6c419eb1a0ce2067c39ece5c37b56/206"
if diff := cmp.Diff(wantInputRootDg, getInputRootDg); diff != "" {
t.Errorf("GetIO returned diff in Inputs' list: (-want +got)\n%s", diff)
}
getInputs := io.Inputs
wantInputs := Inputs{
Paths: map[string]string{"foo.c": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae/3",
"previous_dir/old_target_file": "fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9/3"},
PathSymlinks: map[string]string{"bar.c->previous_dir/old_target_file": "fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9/3"},
}
if diff := cmp.Diff(wantInputs.Paths, getInputs.Paths); diff != "" {
t.Errorf("GetIO returned diff in Input Paths: (-want +got)\n%s", diff)
}
if diff := cmp.Diff(wantInputs.PathSymlinks, getInputs.PathSymlinks); diff != "" {
t.Errorf("GetIO returned diff in Inputs Symlinks: (-want +got)\n%s", diff)
}

getOutput := io.Outputs
wantOutpus := Outputs{
Files: map[string]string{"a/b/out": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae/3"},
FileSymlinks: map[string]string{"a/b/sl->a/b/out": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae/3"},
}
if diff := cmp.Diff(wantOutpus.Files, getOutput.Files); diff != "" {
t.Errorf("GetIO returned diff in Output Files: (-want +got)\n%s", diff)
}
if diff := cmp.Diff(wantOutpus.FileSymlinks, getOutput.FileSymlinks); diff != "" {
t.Errorf("GetIO returned diff in Output File Symlinks: (-want +got)\n%s", diff)
}
}
Loading