diff --git a/android/Android.bp b/android/Android.bp index 4da0f4e74..773aa6ae1 100644 --- a/android/Android.bp +++ b/android/Android.bp @@ -14,6 +14,7 @@ bootstrap_go_package { "soong-bazel", "soong-cquery", "soong-remoteexec", + "soong-response", "soong-shared", "soong-ui-metrics_proto", ], diff --git a/android/rule_builder.go b/android/rule_builder.go index 4b356fa1f..4813d7a10 100644 --- a/android/rule_builder.go +++ b/android/rule_builder.go @@ -28,6 +28,7 @@ import ( "android/soong/cmd/sbox/sbox_proto" "android/soong/remoteexec" + "android/soong/response" "android/soong/shared" ) @@ -459,26 +460,6 @@ func (r *RuleBuilder) depFileMergerCmd(depFiles WritablePaths) *RuleBuilderComma Inputs(depFiles.Paths()) } -// composeRspFileContent returns a string that will serve as the contents of the rsp file to pass -// the listed input files to the command running in the sandbox. -func (r *RuleBuilder) composeRspFileContent(rspFileInputs Paths) string { - if r.sboxInputs { - if len(rspFileInputs) > 0 { - // When SandboxInputs is used the paths need to be rewritten to be relative to the sandbox - // directory so that they are valid after sbox chdirs into the sandbox directory. - return proptools.NinjaEscape(strings.Join(r.sboxPathsForInputsRel(rspFileInputs), " ")) - } else { - // If the list of inputs is empty fall back to "$in" so that the rspfilecontent Ninja - // variable is set to something non-empty, otherwise ninja will complain. The inputs - // will be empty (all the non-rspfile inputs are implicits), so $in will evaluate to - // an empty string. - return "$in" - } - } else { - return "$in" - } -} - // Build adds the built command line to the build graph, with dependencies on Inputs and Tools, and output files for // Outputs. func (r *RuleBuilder) Build(name string, desc string) { @@ -577,9 +558,19 @@ func (r *RuleBuilder) Build(name string, desc string) { // If using an rsp file copy it into the sbox directory. if rspFilePath != nil { - command.CopyBefore = append(command.CopyBefore, &sbox_proto.Copy{ - From: proto.String(rspFilePath.String()), - To: proto.String(r.sboxPathForInputRel(rspFilePath)), + command.RspFiles = append(command.RspFiles, &sbox_proto.RspFile{ + File: proto.String(rspFilePath.String()), + // These have to match the logic in sboxPathForInputRel + PathMappings: []*sbox_proto.PathMapping{ + { + From: proto.String(r.outDir.String()), + To: proto.String(sboxOutSubDir), + }, + { + From: proto.String(PathForOutput(r.ctx).String()), + To: proto.String(sboxOutSubDir), + }, + }, }) } @@ -641,20 +632,30 @@ func (r *RuleBuilder) Build(name string, desc string) { inputs = append(inputs, sboxCmd.inputs...) if r.rbeParams != nil { - var remoteInputs []string - remoteInputs = append(remoteInputs, inputs.Strings()...) - remoteInputs = append(remoteInputs, tools.Strings()...) - remoteInputs = append(remoteInputs, rspFileInputs.Strings()...) + // RBE needs a list of input files to copy to the remote builder. For inputs already + // listed in an rsp file, pass the rsp file directly to rewrapper. For the rest, + // create a new rsp file to pass to rewrapper. + var remoteRspFiles Paths + var remoteInputs Paths + + remoteInputs = append(remoteInputs, inputs...) + remoteInputs = append(remoteInputs, tools...) + if rspFilePath != nil { - remoteInputs = append(remoteInputs, rspFilePath.String()) + remoteInputs = append(remoteInputs, rspFilePath) + remoteRspFiles = append(remoteRspFiles, rspFilePath) + } + + if len(remoteInputs) > 0 { + inputsListFile := r.sboxManifestPath.ReplaceExtension(r.ctx, "rbe_inputs.list") + writeRspFileRule(r.ctx, inputsListFile, remoteInputs) + remoteRspFiles = append(remoteRspFiles, inputsListFile) + // Add the new rsp file as an extra input to the rule. + inputs = append(inputs, inputsListFile) } - inputsListFile := r.sboxManifestPath.ReplaceExtension(r.ctx, "rbe_inputs.list") - inputsListContents := rspFileForInputs(remoteInputs) - WriteFileRule(r.ctx, inputsListFile, inputsListContents) - inputs = append(inputs, inputsListFile) r.rbeParams.OutputFiles = outputs.Strings() - r.rbeParams.RSPFiles = []string{inputsListFile.String()} + r.rbeParams.RSPFiles = remoteRspFiles.Strings() rewrapperCommand := r.rbeParams.NoVarTemplate(r.ctx.Config().RBEWrapper()) commandString = rewrapperCommand + " bash -c '" + strings.ReplaceAll(commandString, `'`, `'\''`) + "'" } @@ -674,7 +675,10 @@ func (r *RuleBuilder) Build(name string, desc string) { var rspFile, rspFileContent string if rspFilePath != nil { rspFile = rspFilePath.String() - rspFileContent = r.composeRspFileContent(rspFileInputs) + // Use "$in" for rspFileContent to avoid duplicating the list of files in the dependency + // list and in the contents of the rsp file. Inputs to the rule that are not in the + // rsp file will be listed in Implicits instead of Inputs so they don't show up in "$in". + rspFileContent = "$in" } var pool blueprint.Pool @@ -1264,13 +1268,12 @@ func (builderContextForTests) Rule(PackageContext, string, blueprint.RuleParams, } func (builderContextForTests) Build(PackageContext, BuildParams) {} -func rspFileForInputs(paths []string) string { - s := strings.Builder{} - for i, path := range paths { - if i != 0 { - s.WriteByte(' ') - } - s.WriteString(proptools.ShellEscape(path)) +func writeRspFileRule(ctx BuilderContext, rspFile WritablePath, paths Paths) { + buf := &strings.Builder{} + err := response.WriteRspFile(buf, paths.Strings()) + if err != nil { + // There should never be I/O errors writing to a bytes.Buffer. + panic(err) } - return s.String() + WriteFileRule(ctx, rspFile, buf.String()) } diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go index 00b0a5752..3df900f88 100644 --- a/android/rule_builder_test.go +++ b/android/rule_builder_test.go @@ -376,8 +376,6 @@ func TestRuleBuilder(t *testing.T) { wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer " + "out_local/module/DepFile out_local/module/depfile out_local/module/ImplicitDepFile out_local/module/depfile2" - wantRspFileContent := "$in" - AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands()) AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs()) @@ -389,8 +387,6 @@ func TestRuleBuilder(t *testing.T) { AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys()) AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String()) - - AssertSame(t, "rule.composeRspFileContent()", wantRspFileContent, rule.composeRspFileContent(rule.RspFileInputs())) }) t.Run("sbox", func(t *testing.T) { @@ -409,8 +405,6 @@ func TestRuleBuilder(t *testing.T) { wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2" - wantRspFileContent := "$in" - AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands()) AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs()) @@ -422,8 +416,6 @@ func TestRuleBuilder(t *testing.T) { AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys()) AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String()) - - AssertSame(t, "rule.composeRspFileContent()", wantRspFileContent, rule.composeRspFileContent(rule.RspFileInputs())) }) t.Run("sbox tools", func(t *testing.T) { @@ -442,8 +434,6 @@ func TestRuleBuilder(t *testing.T) { wantDepMergerCommand := "__SBOX_SANDBOX_DIR__/tools/out/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2" - wantRspFileContent := "$in" - AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands()) AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs()) @@ -455,8 +445,6 @@ func TestRuleBuilder(t *testing.T) { AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys()) AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String()) - - AssertSame(t, "rule.composeRspFileContent()", wantRspFileContent, rule.composeRspFileContent(rule.RspFileInputs())) }) t.Run("sbox inputs", func(t *testing.T) { diff --git a/cmd/sbox/Android.bp b/cmd/sbox/Android.bp index d88505fcc..b8d75ed7a 100644 --- a/cmd/sbox/Android.bp +++ b/cmd/sbox/Android.bp @@ -21,6 +21,7 @@ blueprint_go_binary { deps: [ "sbox_proto", "soong-makedeps", + "soong-response", ], srcs: [ "sbox.go", diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go index f47c6012e..fcc80a94e 100644 --- a/cmd/sbox/sbox.go +++ b/cmd/sbox/sbox.go @@ -32,6 +32,7 @@ import ( "android/soong/cmd/sbox/sbox_proto" "android/soong/makedeps" + "android/soong/response" "github.com/golang/protobuf/proto" ) @@ -218,6 +219,11 @@ func runCommand(command *sbox_proto.Command, tempDir string) (depFile string, er return "", fmt.Errorf("command is required") } + pathToTempDirInSbox := tempDir + if command.GetChdir() { + pathToTempDirInSbox = "." + } + err = os.MkdirAll(tempDir, 0777) if err != nil { return "", fmt.Errorf("failed to create %q: %w", tempDir, err) @@ -228,10 +234,9 @@ func runCommand(command *sbox_proto.Command, tempDir string) (depFile string, er if err != nil { return "", err } - - pathToTempDirInSbox := tempDir - if command.GetChdir() { - pathToTempDirInSbox = "." + err = copyRspFiles(command.RspFiles, tempDir, pathToTempDirInSbox) + if err != nil { + return "", err } if strings.Contains(rawCommand, depFilePlaceholder) { @@ -409,6 +414,83 @@ func copyOneFile(from string, to string, executable bool) error { return nil } +// copyRspFiles copies rsp files into the sandbox with path mappings, and also copies the files +// listed into the sandbox. +func copyRspFiles(rspFiles []*sbox_proto.RspFile, toDir, toDirInSandbox string) error { + for _, rspFile := range rspFiles { + err := copyOneRspFile(rspFile, toDir, toDirInSandbox) + if err != nil { + return err + } + } + return nil +} + +// copyOneRspFiles copies an rsp file into the sandbox with path mappings, and also copies the files +// listed into the sandbox. +func copyOneRspFile(rspFile *sbox_proto.RspFile, toDir, toDirInSandbox string) error { + in, err := os.Open(rspFile.GetFile()) + if err != nil { + return err + } + defer in.Close() + + files, err := response.ReadRspFile(in) + if err != nil { + return err + } + + for i, from := range files { + // Convert the real path of the input file into the path inside the sandbox using the + // path mappings. + to := applyPathMappings(rspFile.PathMappings, from) + + // Copy the file into the sandbox. + err := copyOneFile(from, joinPath(toDir, to), false) + if err != nil { + return err + } + + // Rewrite the name in the list of files to be relative to the sandbox directory. + files[i] = joinPath(toDirInSandbox, to) + } + + // Convert the real path of the rsp file into the path inside the sandbox using the path + // mappings. + outRspFile := joinPath(toDir, applyPathMappings(rspFile.PathMappings, rspFile.GetFile())) + + err = os.MkdirAll(filepath.Dir(outRspFile), 0777) + if err != nil { + return err + } + + out, err := os.Create(outRspFile) + if err != nil { + return err + } + defer out.Close() + + // Write the rsp file with converted paths into the sandbox. + err = response.WriteRspFile(out, files) + if err != nil { + return err + } + + return nil +} + +// applyPathMappings takes a list of path mappings and a path, and returns the path with the first +// matching path mapping applied. If the path does not match any of the path mappings then it is +// returned unmodified. +func applyPathMappings(pathMappings []*sbox_proto.PathMapping, path string) string { + for _, mapping := range pathMappings { + if strings.HasPrefix(path, mapping.GetFrom()+"/") { + return joinPath(mapping.GetTo()+"/", strings.TrimPrefix(path, mapping.GetFrom()+"/")) + } + } + return path +} + // moveFiles moves files specified by a set of copy rules. It uses os.Rename, so it is restricted // to moving files where the source and destination are in the same filesystem. This is OK for // sbox because the temporary directory is inside the out directory. It updates the timestamp diff --git a/cmd/sbox/sbox_proto/sbox.pb.go b/cmd/sbox/sbox_proto/sbox.pb.go index 79bb90c4c..b996481c3 100644 --- a/cmd/sbox/sbox_proto/sbox.pb.go +++ b/cmd/sbox/sbox_proto/sbox.pb.go @@ -86,10 +86,13 @@ type Command struct { CopyAfter []*Copy `protobuf:"bytes,4,rep,name=copy_after,json=copyAfter" json:"copy_after,omitempty"` // An optional hash of the input files to ensure the textproto files and the sbox rule reruns // when the lists of inputs changes, even if the inputs are not on the command line. - InputHash *string `protobuf:"bytes,5,opt,name=input_hash,json=inputHash" json:"input_hash,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + InputHash *string `protobuf:"bytes,5,opt,name=input_hash,json=inputHash" json:"input_hash,omitempty"` + // A list of files that will be copied before the sandboxed command, and whose contents should be + // copied as if they were listed in copy_before. + RspFiles []*RspFile `protobuf:"bytes,6,rep,name=rsp_files,json=rspFiles" json:"rsp_files,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *Command) Reset() { *m = Command{} } @@ -152,6 +155,13 @@ func (m *Command) GetInputHash() string { return "" } +func (m *Command) GetRspFiles() []*RspFile { + if m != nil { + return m.RspFiles + } + return nil +} + // Copy describes a from-to pair of files to copy. The paths may be relative, the root that they // are relative to is specific to the context the Copy is used in and will be different for // from and to. @@ -211,10 +221,110 @@ func (m *Copy) GetExecutable() bool { return false } +// RspFile describes an rspfile that should be copied into the sandbox directory. +type RspFile struct { + // The path to the rsp file. + File *string `protobuf:"bytes,1,req,name=file" json:"file,omitempty"` + // A list of path mappings that should be applied to each file listed in the rsp file. + PathMappings []*PathMapping `protobuf:"bytes,2,rep,name=path_mappings,json=pathMappings" json:"path_mappings,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RspFile) Reset() { *m = RspFile{} } +func (m *RspFile) String() string { return proto.CompactTextString(m) } +func (*RspFile) ProtoMessage() {} +func (*RspFile) Descriptor() ([]byte, []int) { + return fileDescriptor_9d0425bf0de86ed1, []int{3} +} + +func (m *RspFile) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RspFile.Unmarshal(m, b) +} +func (m *RspFile) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RspFile.Marshal(b, m, deterministic) +} +func (m *RspFile) XXX_Merge(src proto.Message) { + xxx_messageInfo_RspFile.Merge(m, src) +} +func (m *RspFile) XXX_Size() int { + return xxx_messageInfo_RspFile.Size(m) +} +func (m *RspFile) XXX_DiscardUnknown() { + xxx_messageInfo_RspFile.DiscardUnknown(m) +} + +var xxx_messageInfo_RspFile proto.InternalMessageInfo + +func (m *RspFile) GetFile() string { + if m != nil && m.File != nil { + return *m.File + } + return "" +} + +func (m *RspFile) GetPathMappings() []*PathMapping { + if m != nil { + return m.PathMappings + } + return nil +} + +// PathMapping describes a mapping from a path outside the sandbox to the path inside the sandbox. +type PathMapping struct { + From *string `protobuf:"bytes,1,req,name=from" json:"from,omitempty"` + To *string `protobuf:"bytes,2,req,name=to" json:"to,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PathMapping) Reset() { *m = PathMapping{} } +func (m *PathMapping) String() string { return proto.CompactTextString(m) } +func (*PathMapping) ProtoMessage() {} +func (*PathMapping) Descriptor() ([]byte, []int) { + return fileDescriptor_9d0425bf0de86ed1, []int{4} +} + +func (m *PathMapping) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PathMapping.Unmarshal(m, b) +} +func (m *PathMapping) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PathMapping.Marshal(b, m, deterministic) +} +func (m *PathMapping) XXX_Merge(src proto.Message) { + xxx_messageInfo_PathMapping.Merge(m, src) +} +func (m *PathMapping) XXX_Size() int { + return xxx_messageInfo_PathMapping.Size(m) +} +func (m *PathMapping) XXX_DiscardUnknown() { + xxx_messageInfo_PathMapping.DiscardUnknown(m) +} + +var xxx_messageInfo_PathMapping proto.InternalMessageInfo + +func (m *PathMapping) GetFrom() string { + if m != nil && m.From != nil { + return *m.From + } + return "" +} + +func (m *PathMapping) GetTo() string { + if m != nil && m.To != nil { + return *m.To + } + return "" +} + func init() { proto.RegisterType((*Manifest)(nil), "sbox.Manifest") proto.RegisterType((*Command)(nil), "sbox.Command") proto.RegisterType((*Copy)(nil), "sbox.Copy") + proto.RegisterType((*RspFile)(nil), "sbox.RspFile") + proto.RegisterType((*PathMapping)(nil), "sbox.PathMapping") } func init() { @@ -222,22 +332,27 @@ func init() { } var fileDescriptor_9d0425bf0de86ed1 = []byte{ - // 268 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0x4f, 0x4b, 0xc3, 0x40, - 0x10, 0xc5, 0xc9, 0x9f, 0xd2, 0x64, 0x6a, 0x7b, 0x18, 0x3c, 0xec, 0x45, 0x09, 0x01, 0x21, 0x45, - 0xe8, 0xc1, 0x6f, 0x60, 0xf5, 0x20, 0x82, 0x97, 0x1c, 0x45, 0x08, 0x9b, 0x64, 0x43, 0x02, 0x4d, - 0x26, 0xec, 0x6e, 0xa0, 0xfd, 0x56, 0x7e, 0x44, 0xd9, 0x49, 0x2a, 0x82, 0xb7, 0x99, 0xdf, 0xe3, - 0xcd, 0x7b, 0x0c, 0x80, 0x29, 0xe9, 0x7c, 0x18, 0x35, 0x59, 0xc2, 0xd0, 0xcd, 0xe9, 0x17, 0x44, - 0x1f, 0x72, 0xe8, 0x1a, 0x65, 0x2c, 0xee, 0x21, 0xaa, 0xa8, 0xef, 0xe5, 0x50, 0x1b, 0xe1, 0x25, - 0x41, 0xb6, 0x79, 0xda, 0x1e, 0xd8, 0xf0, 0x32, 0xd3, 0xfc, 0x57, 0xc6, 0x07, 0xd8, 0xd1, 0x64, - 0xc7, 0xc9, 0x16, 0xb5, 0x1a, 0x9b, 0xee, 0xa4, 0x84, 0x9f, 0x78, 0x59, 0x9c, 0x6f, 0x67, 0xfa, - 0x3a, 0xc3, 0xf4, 0xdb, 0x83, 0xf5, 0x62, 0xc6, 0x47, 0xd8, 0x54, 0x34, 0x5e, 0x8a, 0x52, 0x35, - 0xa4, 0xd5, 0x12, 0x00, 0xd7, 0x80, 0xf1, 0x92, 0x83, 0x93, 0x8f, 0xac, 0xe2, 0x2d, 0xac, 0xaa, - 0xb6, 0xee, 0x34, 0x9f, 0x8d, 0xf2, 0x79, 0x41, 0x01, 0xeb, 0xa5, 0x81, 0x08, 0x12, 0x3f, 0x8b, - 0xf3, 0xeb, 0x8a, 0x7b, 0x60, 0x77, 0x21, 0x1b, 0xab, 0xb4, 0x08, 0xff, 0xdd, 0x8e, 0x9d, 0xfa, - 0xec, 0x44, 0xbc, 0x03, 0xe8, 0x06, 0xd7, 0xbc, 0x95, 0xa6, 0x15, 0x2b, 0xae, 0x1d, 0x33, 0x79, - 0x93, 0xa6, 0x4d, 0xdf, 0x21, 0x74, 0x0e, 0x44, 0x08, 0x1b, 0x4d, 0xbd, 0xf0, 0x38, 0x88, 0x67, - 0xdc, 0x81, 0x6f, 0x49, 0xf8, 0x4c, 0x7c, 0x4b, 0x78, 0x0f, 0xa0, 0xce, 0xaa, 0x9a, 0xac, 0x2c, - 0x4f, 0x4a, 0x04, 0x5c, 0xf5, 0x0f, 0x39, 0xde, 0x7c, 0xf2, 0xc3, 0x0b, 0x7e, 0xf8, 0x4f, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x78, 0x37, 0x3e, 0x6a, 0x7d, 0x01, 0x00, 0x00, + // 342 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x5f, 0x4b, 0xc3, 0x30, + 0x14, 0xc5, 0x69, 0xd7, 0xb9, 0xf6, 0xee, 0x0f, 0x18, 0x7c, 0xc8, 0x8b, 0x32, 0x0a, 0xc2, 0xa6, + 0x30, 0xd0, 0x07, 0xdf, 0x9d, 0x22, 0x22, 0x0c, 0x24, 0xe0, 0x8b, 0x08, 0x25, 0xeb, 0x52, 0x5b, + 0x58, 0x9b, 0x90, 0x64, 0xb0, 0x7d, 0x57, 0x3f, 0x8c, 0xe4, 0xa6, 0xd3, 0x82, 0x2f, 0xbe, 0xdd, + 0x7b, 0x0e, 0xf7, 0xdc, 0x5f, 0xc2, 0x05, 0x30, 0x6b, 0xb9, 0x5f, 0x28, 0x2d, 0xad, 0x24, 0x91, + 0xab, 0xd3, 0x0f, 0x88, 0x57, 0xbc, 0xa9, 0x0a, 0x61, 0x2c, 0x99, 0x43, 0x9c, 0xcb, 0xba, 0xe6, + 0xcd, 0xc6, 0xd0, 0x60, 0xda, 0x9b, 0x0d, 0x6f, 0xc7, 0x0b, 0x1c, 0x78, 0xf0, 0x2a, 0xfb, 0xb1, + 0xc9, 0x25, 0x4c, 0xe4, 0xce, 0xaa, 0x9d, 0xcd, 0x36, 0x42, 0x15, 0xd5, 0x56, 0xd0, 0x70, 0x1a, + 0xcc, 0x12, 0x36, 0xf6, 0xea, 0xa3, 0x17, 0xd3, 0xaf, 0x00, 0x06, 0xed, 0x30, 0xb9, 0x86, 0x61, + 0x2e, 0xd5, 0x21, 0x5b, 0x8b, 0x42, 0x6a, 0xd1, 0x2e, 0x80, 0xe3, 0x02, 0x75, 0x60, 0xe0, 0xec, + 0x25, 0xba, 0xe4, 0x0c, 0xfa, 0x79, 0xb9, 0xa9, 0x34, 0xc6, 0xc6, 0xcc, 0x37, 0x84, 0xc2, 0xa0, + 0x25, 0xa0, 0xbd, 0x69, 0x38, 0x4b, 0xd8, 0xb1, 0x25, 0x73, 0xc0, 0xe9, 0x8c, 0x17, 0x56, 0x68, + 0x1a, 0xfd, 0xc9, 0x4e, 0x9c, 0x7b, 0xef, 0x4c, 0x72, 0x0e, 0x50, 0x35, 0x8e, 0xbc, 0xe4, 0xa6, + 0xa4, 0x7d, 0xc4, 0x4e, 0x50, 0x79, 0xe6, 0xa6, 0x24, 0x57, 0x90, 0x68, 0xa3, 0x32, 0x87, 0x6f, + 0xe8, 0x49, 0xf7, 0x17, 0x98, 0x51, 0x4f, 0xd5, 0x56, 0xb0, 0x58, 0xfb, 0xc2, 0xa4, 0x2f, 0x10, + 0xb9, 0x74, 0x42, 0x20, 0x2a, 0xb4, 0xac, 0x69, 0x80, 0x50, 0x58, 0x93, 0x09, 0x84, 0x56, 0xd2, + 0x10, 0x95, 0xd0, 0x4a, 0x72, 0x01, 0x20, 0xf6, 0x22, 0xdf, 0x59, 0xbe, 0xde, 0x0a, 0xda, 0xc3, + 0x67, 0x75, 0x94, 0xf4, 0x0d, 0x06, 0xed, 0x02, 0x8c, 0x73, 0x5f, 0x7a, 0x8c, 0x73, 0xda, 0x1d, + 0x8c, 0x15, 0xb7, 0x65, 0x56, 0x73, 0xa5, 0xaa, 0xe6, 0xd3, 0xd0, 0x10, 0xd1, 0x4e, 0x3d, 0xda, + 0x2b, 0xb7, 0xe5, 0xca, 0x3b, 0x6c, 0xa4, 0x7e, 0x1b, 0x93, 0xde, 0xc0, 0xb0, 0x63, 0xfe, 0x87, + 0x74, 0x39, 0x7a, 0xc7, 0x33, 0xc9, 0xf0, 0x4c, 0xbe, 0x03, 0x00, 0x00, 0xff, 0xff, 0x83, 0x82, + 0xb0, 0xc3, 0x33, 0x02, 0x00, 0x00, } diff --git a/cmd/sbox/sbox_proto/sbox.proto b/cmd/sbox/sbox_proto/sbox.proto index 695b0e86e..bdf92c669 100644 --- a/cmd/sbox/sbox_proto/sbox.proto +++ b/cmd/sbox/sbox_proto/sbox.proto @@ -47,6 +47,10 @@ message Command { // An optional hash of the input files to ensure the textproto files and the sbox rule reruns // when the lists of inputs changes, even if the inputs are not on the command line. optional string input_hash = 5; + + // A list of files that will be copied before the sandboxed command, and whose contents should be + // copied as if they were listed in copy_before. + repeated RspFile rsp_files = 6; } // Copy describes a from-to pair of files to copy. The paths may be relative, the root that they @@ -58,4 +62,19 @@ message Copy { // If true, make the file executable after copying it. optional bool executable = 3; -} \ No newline at end of file +} + +// RspFile describes an rspfile that should be copied into the sandbox directory. +message RspFile { + // The path to the rsp file. + required string file = 1; + + // A list of path mappings that should be applied to each file listed in the rsp file. + repeated PathMapping path_mappings = 2; +} + +// PathMapping describes a mapping from a path outside the sandbox to the path inside the sandbox. +message PathMapping { + required string from = 1; + required string to = 2; +} diff --git a/response/response.go b/response/response.go index e8ff4b2c9..b65503eb4 100644 --- a/response/response.go +++ b/response/response.go @@ -17,6 +17,7 @@ package response import ( "io" "io/ioutil" + "strings" "unicode" ) @@ -68,3 +69,44 @@ func ReadRspFile(r io.Reader) ([]string, error) { return files, nil } + +func rspUnsafeChar(r rune) bool { + switch { + case 'A' <= r && r <= 'Z', + 'a' <= r && r <= 'z', + '0' <= r && r <= '9', + r == '_', + r == '+', + r == '-', + r == '.', + r == '/': + return false + default: + return true + } +} + +var rspEscaper = strings.NewReplacer(`'`, `'\''`) + +// WriteRspFile writes a list of files to a file in Ninja's response file format. +func WriteRspFile(w io.Writer, files []string) error { + for i, f := range files { + if i != 0 { + _, err := io.WriteString(w, " ") + if err != nil { + return err + } + } + + if strings.IndexFunc(f, rspUnsafeChar) != -1 { + f = `'` + rspEscaper.Replace(f) + `'` + } + + _, err := io.WriteString(w, f) + if err != nil { + return err + } + } + + return nil +} diff --git a/response/response_test.go b/response/response_test.go index 99ce62359..4d1fb417a 100644 --- a/response/response_test.go +++ b/response/response_test.go @@ -94,3 +94,30 @@ func TestReadRspFile(t *testing.T) { }) } } + +func TestWriteRspFile(t *testing.T) { + testCases := []struct { + name string + in []string + out string + }{ + { + name: "ninja rsp file", + in: []string{"a", "b", "@", "foo'bar", `foo"bar`}, + out: "a b '@' 'foo'\\''bar' 'foo\"bar'", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + buf := &bytes.Buffer{} + err := WriteRspFile(buf, testCase.in) + if err != nil { + t.Errorf("unexpected error: %q", err) + } + if buf.String() != testCase.out { + t.Errorf("expected %q got %q", testCase.out, buf.String()) + } + }) + } +}