@@ -770,7 +770,7 @@ func (c *Client) getOutputs(ctx context.Context, actionRes *repb.ActionResult) (
770770 return res .String (), nil
771771}
772772
773- func (c * Client ) getInputTree (ctx context.Context , root * repb.Digest ) (string , [ ]string , error ) {
773+ func (c * Client ) getInputTree (ctx context.Context , root * repb.Digest ) (string , map [ string ]string , error ) {
774774 var res bytes.Buffer
775775
776776 dg , err := digest .NewFromProto (root )
@@ -800,7 +800,7 @@ func (c *Client) getInputTree(ctx context.Context, root *repb.Digest) (string, [
800800 return res .String (), paths , nil
801801}
802802
803- func (c * Client ) flattenTree (ctx context.Context , t * repb.Tree ) (string , [ ]string , error ) {
803+ func (c * Client ) flattenTree (ctx context.Context , t * repb.Tree ) (string , map [ string ]string , error ) {
804804 var res bytes.Buffer
805805 outputs , err := c .GrpcClient .FlattenTree (t , "" )
806806 if err != nil {
@@ -816,8 +816,10 @@ func (c *Client) flattenTree(ctx context.Context, t *repb.Tree) (string, []strin
816816 paths = append (paths , path )
817817 }
818818 sort .Strings (paths )
819+ pathDgs := make (map [string ]string , len (paths ))
819820 for _ , path := range paths {
820821 output := outputs [path ]
822+ dg := output .Digest
821823 var np string
822824 if output .NodeProperties != nil {
823825 np = fmt .Sprintf (" [Node properties: %v]" , prototext.MarshalOptions {Multiline : false }.Format (output .NodeProperties ))
@@ -826,11 +828,16 @@ func (c *Client) flattenTree(ctx context.Context, t *repb.Tree) (string, []strin
826828 res .WriteString (fmt .Sprintf ("%v: [Directory digest: %v]%s\n " , path , output .Digest , np ))
827829 } else if output .SymlinkTarget != "" {
828830 res .WriteString (fmt .Sprintf ("%v: [Symlink digest: %v, Symlink Target: %v]%s\n " , path , output .Digest , output .SymlinkTarget , np ))
831+ path = path + "->" + output .SymlinkTarget
832+ if o , ok := outputs [output .SymlinkTarget ]; ok {
833+ dg = o .Digest
834+ }
829835 } else {
830836 res .WriteString (fmt .Sprintf ("%v: [File digest: %v]%s\n " , path , output .Digest , np ))
831837 }
838+ pathDgs [path ] = fmt .Sprintf ("%v" , dg )
832839 }
833- return res .String (), paths , nil
840+ return res .String (), pathDgs , nil
834841}
835842
836843func (c * Client ) getActionResult (ctx context.Context , actionDigest string ) (* repb.ActionResult , error ) {
@@ -848,3 +855,91 @@ func (c *Client) getActionResult(ctx context.Context, actionDigest string) (*rep
848855 }
849856 return resPb , nil
850857}
858+
859+ // IO is a collection input root Digest, Inputs, and Outputs for an action.
860+ type IO struct {
861+ RootDg string
862+ Inputs * Inputs
863+ Outputs * Outputs
864+ }
865+
866+ // Inputs are input Paths (with digests) of an action. For a symlink, the key is
867+ // `<path>-><target>`, the value is the dg of the target file.
868+ type Inputs struct {
869+ Paths map [string ]string
870+ }
871+
872+ // Outputs are output Files/Dirs (with digests) of an action. For a symlink, the
873+ // key is `<path>-><target>`, the value is the dg of the target file.
874+ type Outputs struct {
875+ Files map [string ]string
876+ Dirs map [string ]string
877+ FileSymlinks map [string ]string
878+ DirSymlinks map [string ]string
879+ }
880+
881+ // GetIO returns the Inputs and Outputs of an action Digest.
882+ func (c * Client ) GetIO (ctx context.Context , actionDigest string ) (* IO , error ) {
883+ acDg , err := digest .NewFromString (actionDigest )
884+ if err != nil {
885+ return nil , fmt .Errorf ("error creating action digest: %w" , err )
886+ }
887+ actionProto := & repb.Action {}
888+ // Get all the Inputs.
889+ if _ , err := c .GrpcClient .ReadProto (ctx , acDg , actionProto ); err != nil {
890+ return nil , fmt .Errorf ("error reading from proto: %w" , err )
891+ }
892+ rootDg , err := digest .NewFromProto (actionProto .GetInputRootDigest ())
893+ if err != nil {
894+ return nil , fmt .Errorf ("error getting input root digest: %w" , err )
895+ }
896+ _ , inputs , err := c .getInputTree (ctx , actionProto .GetInputRootDigest ())
897+ if err != nil {
898+ return nil , err
899+ }
900+ // Get all the Outputs.
901+ resPb , err := c .getActionResult (ctx , actionDigest )
902+ if err != nil {
903+ return nil , err
904+ }
905+
906+ files := map [string ]string {}
907+ dirs := map [string ]string {}
908+ fileSymlinks := map [string ]string {}
909+ dirSymlinks := map [string ]string {}
910+ for _ , f := range resPb .GetOutputFiles () {
911+ if f != nil {
912+ dg , err := digest .NewFromProto (f .GetDigest ())
913+ if err != nil {
914+ log .Errorf ("error creating Digest from proto %v: %w" , f .GetDigest (), err )
915+ }
916+ files [f .GetPath ()] = fmt .Sprintf ("%v" , dg )
917+ }
918+ }
919+ for _ , d := range resPb .GetOutputDirectories () {
920+ if d != nil {
921+ dg , err := digest .NewFromProto (d .GetTreeDigest ())
922+ if err != nil {
923+ log .Errorf ("error creating Digest from proto %v: %w" , d .GetTreeDigest (), err )
924+ }
925+ dirs [d .GetPath ()] = fmt .Sprintf ("%v" , dg )
926+ }
927+ }
928+ for _ , fs := range resPb .GetOutputFileSymlinks () {
929+ if fs != nil {
930+ fileSymlinks [fs .GetPath ()+ "->" + fs .GetTarget ()] = files [fs .GetTarget ()]
931+ }
932+ }
933+ for _ , ds := range resPb .GetOutputDirectorySymlinks () {
934+ if ds != nil {
935+ dirSymlinks [ds .GetPath ()+ "->" + ds .GetTarget ()] = dirs [ds .GetTarget ()]
936+ }
937+ }
938+
939+ return & IO {
940+ RootDg : fmt .Sprintf ("%v" , rootDg ),
941+ Inputs : & Inputs {inputs },
942+ Outputs : & Outputs {Files : files , Dirs : dirs , FileSymlinks : fileSymlinks , DirSymlinks : dirSymlinks },
943+ }, nil
944+
945+ }
0 commit comments