-
Notifications
You must be signed in to change notification settings - Fork 63
/
command.go
157 lines (145 loc) · 4.44 KB
/
command.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package builder
import (
"io"
"os"
"sort"
"syscall"
remoteexecution "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2"
"github.com/buildbarn/bb-storage/pkg/filesystem/path"
"github.com/buildbarn/bb-storage/pkg/util"
"github.com/kballard/go-shellquote"
)
// mkdirEmittingDirectory is an implementation of
// ParentPopulatingDirectory that does little more than track which
// directories are created. For each leaf directory created, it is
// capable of emitting an "mkdir -p" shell command.
type mkdirEmittingDirectory struct {
children map[path.Component]*mkdirEmittingDirectory
}
func (d *mkdirEmittingDirectory) Close() error {
return nil
}
func (d *mkdirEmittingDirectory) EnterParentPopulatableDirectory(name path.Component) (ParentPopulatableDirectory, error) {
if child, ok := d.children[name]; ok {
return child, nil
}
return nil, syscall.ENOENT
}
func (d *mkdirEmittingDirectory) Mkdir(name path.Component, perm os.FileMode) error {
if _, ok := d.children[name]; ok {
return syscall.EEXIST
}
d.children[name] = &mkdirEmittingDirectory{
children: map[path.Component]*mkdirEmittingDirectory{},
}
return nil
}
func (d *mkdirEmittingDirectory) emitCommands(trace *path.Trace, w io.StringWriter) error {
if len(d.children) > 0 {
// This directory has children, so there's no need to
// emit an 'mkdir -p' call for this directory.
l := make(path.ComponentsList, 0, len(d.children))
for name := range d.children {
l = append(l, name)
}
sort.Sort(l)
for _, name := range l {
if err := d.children[name].emitCommands(trace.Append(name), w); err != nil {
return err
}
}
} else if trace != nil {
// This directory has no children and it's not the root
// directory. Emit a single 'mkdir -p' call.
if _, err := w.WriteString("mkdir -p "); err != nil {
return err
}
if _, err := w.WriteString(shellquote.Join(trace.GetUNIXString())); err != nil {
return err
}
if _, err := w.WriteString("\n"); err != nil {
return err
}
}
return nil
}
// ConvertCommandToShellScript writes a POSIX shell script to a
// StringWriter that causes a process to be launched in the way encoded
// in a Command message.
//
// Because input roots do not explicitly store parent directories of
// outputs, and actions generally assume that they exist, the resulting
// shell script may contain one or more "mkdir -p" calls to create those
// directories prior to execution.
func ConvertCommandToShellScript(command *remoteexecution.Command, w io.StringWriter) error {
// Preamble.
if _, err := w.WriteString("#!/bin/sh\nset -e\n"); err != nil {
return err
}
// Create parent directories of outputs.
outputHierarchy, err := NewOutputHierarchy(command)
if err != nil {
return err
}
d := mkdirEmittingDirectory{
children: map[path.Component]*mkdirEmittingDirectory{},
}
if err := outputHierarchy.CreateParentDirectories(&d); err != nil {
return err
}
if err := d.emitCommands(nil, w); err != nil {
return err
}
// Switch to the right working directory.
workingDirectoryParser, err := path.NewUNIXParser(command.WorkingDirectory)
if err != nil {
return util.StatusWrap(err, "Invalid working directory")
}
workingDirectory, scopeWalker := path.EmptyBuilder.Join(path.VoidScopeWalker)
if err := path.Resolve(workingDirectoryParser, scopeWalker); err != nil {
return util.StatusWrap(err, "Failed to resolve working directory")
}
if _, err := w.WriteString("cd "); err != nil {
return err
}
if _, err := w.WriteString(shellquote.Join(workingDirectory.GetUNIXString())); err != nil {
return err
}
if _, err := w.WriteString("\n"); err != nil {
return err
}
// Set environment variables.
for _, environmentVariable := range command.EnvironmentVariables {
if _, err := w.WriteString("export "); err != nil {
return err
}
if _, err := w.WriteString(environmentVariable.Name); err != nil {
return err
}
if _, err := w.WriteString("="); err != nil {
return err
}
if _, err := w.WriteString(shellquote.Join(environmentVariable.Value)); err != nil {
return err
}
if _, err := w.WriteString("\n"); err != nil {
return err
}
}
// Execute the command.
if _, err := w.WriteString("exec"); err != nil {
return err
}
for _, argument := range command.Arguments {
if _, err := w.WriteString(" "); err != nil {
return err
}
if _, err := w.WriteString(shellquote.Join(argument)); err != nil {
return err
}
}
if _, err := w.WriteString("\n"); err != nil {
return err
}
return nil
}