github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/internal/pkg/inputprocessor/action/clangcl/flagsparser.go (about) 1 // Copyright 2023 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package clangcl 16 17 import ( 18 "context" 19 "fmt" 20 "path/filepath" 21 "strings" 22 23 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor/args" 24 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor/clangparser" 25 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor/flags" 26 "github.com/bazelbuild/reclient/internal/pkg/rsp" 27 ) 28 29 const clangOption = "-clang:" 30 31 // parseFlags is used to translate the given action command into 32 // clang-cl compiler options, so that they can be used during 33 // input processing. 34 func parseFlags(ctx context.Context, command []string, workingDir, execRoot string) (*flags.CommandFlags, error) { 35 numArgs := len(command) 36 if numArgs < 2 { 37 return nil, fmt.Errorf("insufficient number of arguments in command: %v", command) 38 } 39 40 res := &flags.CommandFlags{ 41 ExecutablePath: command[0], 42 WorkingDirectory: workingDir, 43 ExecRoot: execRoot, 44 } 45 46 var state clangCLState 47 s := clangparser.New(command) 48 for s.HasNext() { 49 if err := state.handleClangCLFlags(res, s); err != nil { 50 return nil, err 51 } 52 } 53 state.Finalize(res) 54 return res, nil 55 } 56 57 type clangCLState struct { 58 clangparser.State 59 } 60 61 func shouldPopLastFlag(f *flags.Flag) bool { 62 if f.Key == clangOption && f.Value == "-MF" && f.Joined && f.OriginalKey() == "/clang:" { 63 return true 64 } 65 return false 66 } 67 68 func (s *clangCLState) handleClangCLFlags(cmdFlags *flags.CommandFlags, scanner *args.Scanner) error { 69 nextRes := scanner.ReadNextFlag() 70 f := &flags.CommandFlags{ 71 ExecRoot: cmdFlags.ExecRoot, 72 WorkingDirectory: cmdFlags.WorkingDirectory, 73 } 74 handleArgFunc := func(sc *args.Scanner) error { 75 prev, cur := sc.PrevResult, sc.CurResult 76 flag, values := cur.NormalizedKey, cur.Values 77 if prev.NormalizedKey == clangOption { 78 if prev.Values[0] == "-MF" && flag == clangOption { 79 if lastFlg := cmdFlags.Flags[len(cmdFlags.Flags)-1]; shouldPopLastFlag(lastFlg) { 80 // Pop the last flag, if it is &{-clang: -MF true /clang:}, because we handle it here. 81 cmdFlags.Flags = cmdFlags.Flags[:len(cmdFlags.Flags)-1] 82 } 83 // Overwrite should be OK here as -MF only appear once in a clang-cl cmd. 84 cmdFlags.EmittedDependencyFile = values[0] 85 f.OutputFilePaths = append(f.OutputFilePaths, values[0]) 86 return nil 87 } 88 } 89 switch flag { 90 case "-Fo": 91 f.OutputFilePaths = append(f.OutputFilePaths, values[0]) 92 return nil 93 } 94 return s.State.HandleClangFlags(&cur, f, false) 95 } 96 97 // Check if this is a rsp file that needs processing or just a normal flag. 98 if strings.HasPrefix(nextRes.Args[0], "@") { 99 rspFile := nextRes.Args[0][1:] 100 cmdFlags.Dependencies = append(cmdFlags.Dependencies, rspFile) 101 if !filepath.IsAbs(rspFile) { 102 rspFile = filepath.Join(cmdFlags.ExecRoot, cmdFlags.WorkingDirectory, rspFile) 103 } 104 cmdFlags.Flags = append(cmdFlags.Flags, &flags.Flag{Value: nextRes.Args[0]}) 105 if err := rsp.ParseWithFunc(rspFile, *scanner, handleArgFunc); err != nil { 106 return err 107 } 108 // We don't want to pass along the flags that were in the rsp file, just the 109 // rsp file itself as a flag. 110 } else { 111 if err := handleArgFunc(scanner); err != nil { 112 return err 113 } 114 // We want to pass along flags that were on the command line. 115 cmdFlags.Flags = append(cmdFlags.Flags, f.Flags...) 116 } 117 cmdFlags.Dependencies = append(cmdFlags.Dependencies, f.Dependencies...) 118 cmdFlags.OutputDirPaths = append(cmdFlags.OutputDirPaths, f.OutputDirPaths...) 119 cmdFlags.OutputFilePaths = append(cmdFlags.OutputFilePaths, f.OutputFilePaths...) 120 cmdFlags.IncludeDirPaths = append(cmdFlags.IncludeDirPaths, f.IncludeDirPaths...) 121 cmdFlags.TargetFilePaths = append(cmdFlags.TargetFilePaths, f.TargetFilePaths...) 122 return nil 123 }